]> git.donarmstrong.com Git - lib.git/blob - emacs_el/tiny-tools/tiny/tinylib.el
add tiny-tools
[lib.git] / emacs_el / tiny-tools / tiny / tinylib.el
1 ;;; tinylib.el --- Library of general functions
2
3 ;; This file is not part of Emacs
4
5 ;;{{{ Id
6
7 ;; Copyright (C)    1995-2007 Jari Aalto
8 ;; Keywords:        extensions
9 ;; Author:          Jari Aalto
10 ;; Maintainer:      Jari Aalto
11 ;;
12 ;; To get information on this program, call M-x tinylib-version
13 ;; 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 ;;; Install:
38
39 ;; ........................................................ &t-install ...
40 ;; Put this file to the package that you're developing. This file is
41 ;; is mostly for developers.
42 ;;
43 ;;     (require 'tinylibm)  ;; No mistake here, you load `m' library
44 ;;
45 ;; If you have any questions or feedback, use this function
46 ;;
47 ;;      M-x tinylib-submit-feedback
48
49 ;;}}}
50 ;;{{{ Documentation
51
52 ;; ..................................................... &t-commentary ...
53
54 ;;; Commentary:
55
56 ;;  Preface, 1995
57 ;;
58 ;;      This is library, so the package itself does nothing,
59 ;;      there may be some interactive functions.
60 ;;      There is a issue of Emacs and XEmacs differences multiplied with
61 ;;      different OS platforms, like Cygwin and native Win32. In order to
62 ;;      reuse the code in modules and to keep up with the Emacs/XEmacs
63 ;;      interface and different releases, the general function can be found
64 ;;      from these libraries.
65 ;;
66 ;;  Defining a minor mode
67 ;;
68 ;;      This library provides Emacs/XEmacs comatible minor mode
69 ;;      since 1995. There is one macro that defines all minor mode
70 ;;      variables and function.
71 ;;
72 ;;          (eval-and-compile   ;; So that defvars and defuns are seen
73 ;;            (ti::macrof-minor-mode-wizard
74 ;;             ;;
75 ;;             ;; 1. prefix for variables and functions
76 ;;             ;; 2. Modeline name
77 ;;             ;; 3. prefix key for mode.
78 ;;             ;; 4. Menu bar name
79 ;;             ;; 5. <forget this>
80 ;;             ;;
81 ;;             "xxx-" " xxxModeline" "\C-cx" "xxxMenubar" nil
82 ;;
83 ;;             "XXX minor mode. Does fancy things."  ;; mode description
84 ;;              Defined keys:
85 ;;              \\{xxx-mode-map}
86 ;;              "
87 ;;
88 ;;              "XXX help"    ;; message displayed when user calls mode
89 ;;              nil           ;; Forms When minor mode is called
90 ;;
91 ;;             ;; This is used by easy-menu.el and defines menu items.
92 ;;             (list
93 ;;              xxx-mode-easymenu-name
94 ;;              ["Eval whole buffer" xxx-eval-current-buffer    t]
95 ;;              ..)
96 ;;
97 ;;             ;;  this block defines keys to the mode. The mode minor map is
98 ;;             ;;  locally bound to 'map' symbol.
99 ;;             (progn
100 ;;               (define-key map "-" 'xxx-eval-current-buffer)
101 ;;               (define-key map "=" 'xxx-calculate))))
102 ;;
103 ;;  Defining minor mode step by step
104 ;;
105 ;;      If you want to take more personal control over the minor mode
106 ;;      creation, here I explain step by step what macros you need to include
107 ;;      in your package to get minor mode created, This takes only
108 ;;      half an hour and you have basic minor mode ready. Put all
109 ;;      following calls near top of your file. We suppose we're
110 ;;      creating XXX-mode.
111 ;;
112 ;;      _[1]_ First, define standard variables for minor mode.
113 ;;
114 ;;          (ti::macrov-minor-mode "xxxModeline" "\C-cx" "xxxMenubar")
115 ;;
116 ;;      After that user has  following varibles for customization. (for
117 ;;      complete list of created variables, see the macro's description)
118 ;;
119 ;;          ;; Don't like default key combo C-c x
120 ;;          (setq xxx-mode-prefix-key "\C-cm")
121 ;;
122 ;;          ;; The default mode string was too long, use shorter.
123 ;;          (setq xxx-mode-name "xxx")
124 ;;
125 ;;          ;;  When mode runs, I want to do this.
126 ;;          (add-hook 'xxx-mode-hook 'my-xxx-settings)
127 ;;
128 ;;          ;;  I want to add additional keys
129 ;;          (add-hook 'xxx-mode-define-keys-hook 'my-xxx-keys)
130 ;;
131 ;;      _[2]_ Next we need installation function, which installs our minor
132 ;;      mode so that emacs is aware of it. The minor mode functions,
133 ;;      xxx-mode, will call xxx-mode-define-keys-hook which takes care of
134 ;;      defining keys to key maps and creating menus with easy-menu.el. The
135 ;;      separate installation function is used, because it takes care of
136 ;;      emacs specific things and if called with additional argument, it
137 ;;      also knows how to remove the mode gracefully.
138 ;;
139 ;;          (ti::macrof-minor-mode-install
140 ;;           xxx-install-mode
141 ;;           xxx-mode
142 ;;           xxx-mode-map
143 ;;           xxx-mode-name
144 ;;           xxx-mode-define-keys-hook)
145 ;;
146 ;;      _[3]_ Do we have additional files attached to the end of package?
147 ;;      If yes, then we need pgp-tar unpack function too.
148 ;;
149 ;;          (ti::macrof-install-pgp-tar "xxx-install-files"  "xxx.el")
150 ;;
151 ;;      _[4]_ Finally the user callable minor mode function is created.
152 ;;
153 ;;          (ti::macrof-minor-mode
154 ;;           xxx-mode
155 ;;           "XXX minor mode. Does fancy things."
156 ;;           Defined keys:
157 ;;           \\{xxx-:mode-map}
158 ;;           "
159 ;;           xxx-install-mode
160 ;;           xxx-mode
161 ;;           xxx-mode-name
162 ;;           xxx-mode-prefix-key
163 ;;           xxx-mode-easymenu
164 ;;           nil                        ;Yes, print turn on/off message
165 ;;           "XXX help"
166 ;;           xxx-mode-hook)
167 ;;
168 ;;      That's it. when you execute all these statements you have basic core
169 ;;      for emacs minor mode. The only things missing is the actual
170 ;;      functions that the minor mode commands uses and the function that
171 ;;      defines keys and menus for the minor mode. You probably want to
172 ;;      start from the function that defines keys and menus. Here is ready
173 ;;      macro for that too.
174 ;;
175 ;;          (add-hook' xxx-mode-define-keys-hook 'xxx-mode-define-keys)
176 ;;
177 ;;          (ti::macrof-define-keys
178 ;;           "xxx-mode-define-keys"
179 ;;           'xxx-:mode-prefix-map
180 ;;           'xxx-:mode-prefix-key
181 ;;
182 ;;           'xxx-:easymenu
183 ;;           'xxx-:easymenu-name
184 ;;           "Programming help menu."
185 ;;           (list
186 ;;            xxx-:easymenu-name
187 ;;            ["Eval whole buffer" xxx-eval-current-buffer    t]
188 ;;            ..)
189 ;;           '(progn
190 ;;              (define-key map "-" 'xxx-eval-current-buffer)
191 ;;              (define-key map "=" 'xxx-calculate)
192 ;;              ..))
193
194 ;;}}}
195
196 ;;; Change Log:
197
198 ;;; Code:
199
200 ;;{{{ require
201
202 ;;; ......................................................... &require ...
203
204 (require 'tinylibm)                     ;macro package
205
206 (eval-when-compile
207   (ti::package-use-dynamic-compilation)
208   (when (and (ti::xemacs-p)
209              (byte-compiling-files-p))
210     (message "\n\
211   ** tinylib.el: [Note] It is safe to ignore Emacs dependant ange-ftp function
212                  compilation errors.")))
213
214 (eval-and-compile
215
216   (defvar generated-autoload-file) ;; See autoload.el
217   (defvar flyspell-mode)
218
219   (autoload 'vc-name         "vc-hooks")
220   (autoload 'vc-file-getprop "vc-hooks")
221
222   ;;  Can't autoload timer, because various calls in this lib are behind
223   ;;  ti::funcall --> Bytecompiler doesn't see them.
224
225   (ti::package-package-require-timer)   ;XEmacs and Emacs differencies
226
227   (cond
228    ((ti::xemacs-p)
229
230     ;;  Ange-ftp function used in this package won't work in XEmacs.
231     ;;  The ange functions used for backgroung ftp downloads
232     ;;  and low level calling calling of ange functions. Currently used in
233     ;;  one pacakge: tinydired.el, which let's you donwload/upload
234     ;;  files at the background.
235
236     (require 'efs-auto nil 'noerr)
237     (autoload 'read-passwd "passwd" "" t))
238
239    ((ti::emacs-p)
240     (defvar ange-ftp-process-result                 nil)
241     (defvar ange-ftp-ascii-hash-mark-size           1024)
242     (defvar ange-ftp-binary-hash-mark-size          1024)
243     (defvar ange-ftp-process-busy                   nil)
244     (autoload 'ange-ftp-process-handle-line         "ange-ftp")
245     (autoload 'ange-ftp-get-process                 "ange-ftp")
246     (autoload 'ange-ftp-ftp-name                    "ange-ftp")
247     (autoload 'ange-ftp-real-file-name-as-directory "ange-ftp")
248     (autoload 'ange-ftp-expand-dir                  "ange-ftp")
249     (autoload 'ange-ftp-ftp-process-buffer          "ange-ftp")
250     (autoload 'ange-ftp-set-binary-mode             "ange-ftp")
251     (autoload 'ange-ftp-send-cmd                    "ange-ftp")
252     (autoload 'ange-ftp-cd                          "ange-ftp")
253     (autoload 'ange-ftp-raw-send-cmd                "ange-ftp"))))
254
255 ;;}}}
256 ;;{{{ setup: -- variables
257
258 ;;; ....................................................... &v-private ...
259
260 (defconst ti::var-syntax-info
261   '((?\  "Whitespace")
262     (?-  "Whitespace")
263     (?w  "Word")
264     (?_  "Symbol, variables and commands")
265     (?.  "Punctuation, separate symbols from one another")
266     (?(  "Open parenthesis")
267       (?)  "Close parenthesis")
268     (?\" "String quote, string as a single token")
269     (?\\ "Escape")
270     (?/  "Character quote, only the character immediately following.")
271     (?$  "Paired delimiter, like string quote, chars between are not suppressed")
272     (?<  "Comment starter")
273     (?>  "Comment ender")
274     (?@  "Inherit from standard syntax table"))
275   "Short syntax definition table ((CLASS . DESC) ..).")
276
277 ;;; ........................................................ &v-public ...
278 ;;; User configurable
279
280 (defvar ti::var-x-coord 170
281   "*Default X menu coordinate.")
282
283 (defvar ti::var-y-coord 170
284   "*Default X menu coordinate.")
285
286 ;; Make this invisible by default, note leading space.
287 (defvar ti::var-passwd-buffer " *passwd-entries*"
288   "*Contents of password file.")
289
290 ;;}}}
291 ;;{{{ setup: -- version
292
293 ;;; ....................................................... &v-version ...
294 ;;; These are not library funcs, so they have normal 'tinylib-' prefix
295
296 (defconst tinylib-version
297   (substring "$Revision: 2.107 $" 11 15)
298   "Latest version number.")
299
300 (defconst tinylib-version-id
301   "$Id: tinylib.el,v 2.107 2007/05/07 10:50:07 jaalto Exp $"
302   "Latest modification time and version number.")
303
304 ;;; ----------------------------------------------------------------------
305 ;;;
306 (defun tinylib-version (&optional arg)
307   "Show version information. ARG will instruct to print message to echo area."
308   (interactive "P")
309   (ti::package-version-info "tinylib.el" arg))
310
311 ;;; ----------------------------------------------------------------------
312 ;;;
313 (defun tinylib-submit-feedback ()
314   "Submit suggestions, error corrections, impressions, anything..."
315   (interactive)
316   (ti::package-submit-feedback "tinylib.el"))
317
318 ;;}}}
319
320 ;;; ########################################################### &funcs ###
321
322 ;;{{{ defsubst
323
324 ;;; ........................................................ &defsubst ...
325 ;;; inlined functions, they must be introduced before used
326
327 ;;; ----------------------------------------------------------------------
328 ;;;
329 (defun ti::string-trim-blanks (string &optional middle)
330   "Strip leading, trailing and middle spaces.
331 Input:
332
333   MIDDLE  if non-nil, trim blanks in the middle too and convert
334           tabs to spaces."
335   (when (stringp string)
336     ;;  Strip leading and trailing
337     (if (string-match "^[ \t\f\r\n]*\\(.+[^ \t\f\r\n]\\)" string)
338         (setq string (match-string 1 string)))
339     (when middle
340       ;; middle spaces
341       (setq string (replace-regexp-in-string "[\t\r\n]" " " string))
342       (setq string (replace-regexp-in-string "  +" " " string)))
343     string))
344
345 ;;; ----------------------------------------------------------------------
346 ;;;
347 ;;; (ti::string-verify-ends "Z" "\\." "." 'beg)
348 ;;; (ti::string-verify-ends "dir" "/")
349 ;;;
350 (defun ti::string-verify-ends (str re &optional add-str beg)
351   "Make sure STR match RE and add ADD-STR string to it when necessary.
352 if ADD-STR is not given, adds RE to the string.
353
354 Default is to check end of string, Optionally BEG of string.
355 The RE may not include anchors.
356
357 Examples:
358    making sure directory has ending slash
359    (ti::string-verify-ends \"dir\" \"/\")       --> \"dir/\"
360
361    Making sure, time is zero based:
362    (ti::string-verify-ends \"7\" \"0\" nil 'beg) --> \"07\"
363
364    This does not give you the rsult you assume!
365    because the second parameter, \"  \", is regexp that is tried.
366    This function can't know that there is only \" \" space at front,
367    since the regexp dind't match.
368
369    (ti::string-verify-ends \" padd\" \"  \" nil 'beg)
370    --> \"   padd\"
371
372 Return:
373   str    possibly modified"
374   (let* ((RE  (if beg
375                   (concat "\\`" re)
376                 (concat re "\\'")))
377          (add (or add-str re)))         ;which one to add.
378     (if (string-match RE str)
379         str
380       (if beg
381           (concat add str)
382         (concat str add)))))
383
384 ;;; ----------------------------------------------------------------------
385 ;;; - Originally there was own function for this; but now
386 ;;;   it uses general func verify...
387 ;;; - The main purpose of this function is that when you cat words
388 ;;;   together, you can be sure they have COUNT spaces.
389 ;;; - kinda sprintf...
390 ;;;
391 (defsubst ti::string-add-space (str &optional end count)
392   "Add space to the beginning of STR if there isn't one.
393 Optionally adds to the END. COUNT is by default 1
394
395 If string length is 0, do nothing."
396   (let* ((count  (or count 1))
397          (padd   (make-string count ?\ )))
398     (ti::string-verify-ends str padd padd (not end))))
399
400 ;;; ----------------------------------------------------------------------
401 ;;;
402 (defun ti::string-remove-whitespace (string)
403   "Squeezes empty spaces around beginning and end of STRING.
404 If STRING is not stringp, then returns STRING as is."
405   (when string
406     (replace-regexp-in-string
407      "^[ \t\r\n]+" ""
408      (replace-regexp-in-string
409       "[ \t\r\n]+\\'" "" string))))
410
411 ;;; ----------------------------------------------------------------------
412 ;;;
413 (defun ti::string-mangle (string)
414   "Mangle STRING ie. make STRING unreadable.
415 Same mangling is performed for the same STRING. Mangling can't be reversed."
416   (let* ((ch-list (coerce string 'list))
417
418          ;; (coerce list 'string) to get list of ints to string
419
420          (abc "zaybxcwdveuftgshriqjpkolnm0918273645ZAYBXCWDVEUFTGSHRIQJPKOLNM")
421          (len (length abc))
422          (ret "")
423          x)
424     (dolist (ch ch-list)
425       (setq x (% (char-to-int ch) len))
426       (setq ret (concat ret (substring abc x (1+ x)))))
427     ret))
428
429 ;;; ----------------------------------------------------------------------
430 ;;; #todo: Use replace-regexp-in-string
431 ;;;
432 (defsubst ti::string-regexp-delete (re str &optional level)
433   "Remove all that match RE from STR at subexpression LEVEL."
434   (while (string-match re str)
435     (setq str (ti::replace-match (or level 0) nil str)))
436   str)
437
438 ;;}}}
439 ;;{{{ Conversions
440
441 ;;; ##################################################### &Conversions ###
442
443 ;;; ----------------------------------------------------------------------
444 ;;; Try
445 ;;;     (setq str "%s")
446 ;;;     (message str)          ;; suppose you don't know what's in there
447 ;;;
448 ;;; and you get error... use (message (ti::string-format-percent str))
449 ;;;
450 (defun ti::string-format-percent (str)
451   "Convert STR to message string, doubling diffucult charactes, like % and \\."
452   (let* ((len  (length str))
453          (i    0)
454          (ret  str)
455          ch-string
456          extra
457          ch)
458     (cond
459      ((string-match "[%\\]" str)        ;only now do
460       (setq ret "")
461       (while (< i len)
462         (setq ch        (aref str i)
463               ch-string (char-to-string ch)
464               extra     "")
465         (if (char= ch ?%)
466             (setq extra ch-string))
467         (setq ret (concat ret ch-string extra))
468         (incf i))))
469     ret))
470
471 ;;; ----------------------------------------------------------------------
472 ;;;
473 (defun ti::string-url-to-ange-ftp (str)
474   "Converts URL STR into ange ftp address.
475
476 Eg:
477    ftp://some.site
478    ftp://some.site/pub/users/foo/emacs/some.el
479    ftp://some.site:/pub/users/foo/emacs/some.el
480    ftp://ftp@some.site/pub/users/foo/emacs/some.el
481    ftp://ftp@some.site:/pub/users/foo/emacs/some.el
482
483    -->
484    /ftp@some.site:/
485    /ftp@some.site:/pub/users/foo/emacs/some.el
486
487 Return:
488   string
489   nil"
490   (let* (ref
491          idx
492          login
493          host
494          dir
495          ret)
496     (cond
497      ( ;;
498       (string-match "ftp:/\\(/.*@\\)\\([^/]+:\\)\\(/.*\\)" str)
499       (setq login (match-string 1 str) ;; case 3
500             host  (match-string 2 str)
501             dir   (match-string 3 str)
502             ret   (concat login host dir)))
503      ( ;;
504       (and  (string-match "ftp:/\\(/.*@\\)\\(.*\\)" str)
505             (setq login (match-string 1 str) ;; case 4
506                   ref   (match-string 2 str)))
507       (setq idx  (ti::string-index ref ?/ ))
508       (setq host (or host (substring ref 0 idx)))
509       (setq dir  (substring ref idx))
510       (setq ret (concat (or login "/ftp@") host ":" dir)))
511      ( ;; ftp://some.site/pub/users/foo/emacs/some.el
512       (and (string-match "ftp://\\([^@/]+\\)\\(:?/.*\\)" str)
513            (setq host (match-string 1 str)
514                  dir  (match-string 2 str)))
515       (setq ret (concat
516                  "/ftp@" host
517                  (if (ti::string-index dir ?:) "" ":") ;add colon if needed
518                  dir)))
519
520      ( ;; ftp://some.site
521       (and (string-match "ftp://\\([^@:]+\\)$" str)
522            (setq host (match-string 1 str)))
523       (setq ret (concat "/ftp@" host ":/"))))
524
525     ret))
526
527 ;;; ----------------------------------------------------------------------
528 ;;; #todo: there seems to be c-backslash-region
529 ;;;
530 (defun ti::buffer-backslash-fix-paragraph
531   (&optional target-column stop-func verb)
532   "Fix \\ lines in current paragraph.
533 The first \\ Tells what the target column is.  If there is no \\ anywhere
534 in the paragraph, this function does nothing.
535
536 Input:
537
538   TARGET-COLUMN         position backslashes to this column, if possible.
539                         if \\[universal-argument] or negative number,
540                         remove backslashes.
541   STOP-FUNC             If this function returns non-nil, then stop adding
542                         backslashes. It is called prior the line is handled.
543   VERB                  Verbose messages.
544
545 Example 1:
546
547   This is \\
548   Here is another       ;; Note missing \\
549   and \\                ;; Note, extra \\, should not be there
550
551   Will be formatted as:
552
553   This is \\            ;; Target column, when TARGET-COLUMN is nil
554   Here is another \\    ;; Because the target-cool couldn't be set.
555   end
556
557 Example 2:
558
559   This is               ;; Ignored, no \\
560   Here is another \\    ;; Target starts here
561   And still..
562   end
563
564   Will be formatted as:
565
566   This is
567   Here is another \\
568   And still..     \\    ;; Added
569   end
570
571 Example 3:
572
573 All the lines in this procmail example are together, but it would be wrong
574 to add final \\ to the end of ')'. The STOP-FUNC can make sure about that.
575
576 {
577    :0 h # this is procmail code
578    * condition
579    | ( formail -rt    \\
580        MORE-OPTIONS | \\
581        $SENDMAIL -t)
582 }"
583   (interactive "*P")
584   (let* ((point   (point))
585          (cs      (or comment-start "[ \t]*"))
586          (stop-re (format "^\\(%s\\)?[ \t]*$" cs)) ;Paragraph end
587          (kill-it (or (ti::listp target-column)
588                       (and (integerp target-column)
589                            (< target-column 0))))
590
591          indent-tabs-mode               ;No tabs allowed
592          beg
593          col-target
594          col-now
595          col-word
596          ad-it)
597     (ti::verb)
598     ;; ............................................... paragraph start ...
599     (beginning-of-line)
600     (while (and (not (eobp))
601                 (not (looking-at stop-re)))
602       (forward-line -1))
603     ;; .................................... forward to first backslash ...
604     ;;  Skip comment lines and emtuy line forward.
605     (while (and (not (eobp))
606                 (looking-at stop-re))
607       (forward-line 1))
608     (when (eq major-mode 'makefile-mode)
609       (if (looking-at ".*:") ;;  Go path the TARGET: RULE
610           (forward-line 1)))
611     ;; ... ... ... ... ... ... ... ... ... ... .. &starting target-col ...
612     (save-excursion                     ;Find the starting \\
613       (beginning-of-line)
614       (while (and (not (eobp))
615                   (not (looking-at ".*[\\][ \t]*$"))
616                   (not (looking-at stop-re)))
617         (forward-line 1))
618       (setq beg (point)))
619     (goto-char beg)                     ;We landed here
620     (cond
621      ((not (looking-at ".*[\\]"))
622       (message "\
623 Fix backslash: Nothing to do; no \ mark at the paragraph beginning."))
624      (t
625       (goto-char (match-end 0))
626       (backward-char 1)
627       (setq col-target (or (and
628                             ;; User gave this value
629                             (integerp target-column)
630                             (>= target-column 0)
631                             target-column)
632                            (current-column))) ;; use column from code them
633       (when kill-it
634         (delete-char 1)
635         (delete-horizontal-space))
636       ;;  there was old starting \\, but not in the right column. Fix it,
637       ;;  but only if it was far left.
638       ;;
639       ;;    txt txt \     ;; this line is too far right
640       ;;      T \         ;; The target column user wanted was T
641       (when (and (null kill-it)
642                  (not  (eq (current-column) col-target)))
643         (delete-region (point) (line-end-position))
644         (move-to-column col-target)
645         (when (or (null stop-func)
646                   (and stop-func
647                        (null (funcall stop-func))))
648           (insert "\\")))
649       (unless (looking-at "$")          ;Remove garbage
650         (delete-region (point) (line-end-position)))
651       (beginning-of-line)
652       ;; ... ... ... ... ... ... ... ... ... ... ... ... .. loop-lines . .
653       ;;  Empty line terminates
654       (while (and
655               (not (eobp))
656               (not (looking-at stop-re))
657               (or (null stop-func)
658                   (and stop-func
659                        (null (funcall stop-func)))))
660         (save-excursion                 ;Peek next line
661           (forward-line 1)
662           (setq ad-it (not (looking-at stop-re))))
663         ;; ... ... ... ... ... ... ... ... ... ... ... fix backslashes ...
664         (cond
665          (kill-it
666           (when (looking-at ".*[\\]")
667             (goto-char (match-end 0)) (backward-char 1)
668             (delete-char 1)
669             (delete-horizontal-space)))
670          ((looking-at ".*[\\]")
671           (goto-char (match-end 0)) (backward-char 1)
672           (setq col-now (current-column))
673           ;;  Where is the word start?
674           (skip-chars-backward " \t\\")
675           (untabify (point) (line-end-position))
676           (setq col-word (current-column))
677           (cond
678            ((and (eq col-now col-target)
679                  (null ad-it))
680             ;;  remove Extra  \\
681             (move-to-column col-now)
682             (delete-char 1)
683             (delete-horizontal-space))
684            ((not (eq col-now col-target))
685             ;;    This \
686             ;;    GFile.here \   < This is further right
687             (cond
688              ((> col-word col-target))  ;Do nothing, can't "line up"
689              (t
690               (move-to-column (min col-target col-now))
691               (delete-region (point) (line-end-position))
692               (when ad-it
693                 (ti::buffer-move-to-col col-target)
694                 (insert "\\")))))))
695          ;; ... ... ... ... ... ... ... ... ... ... .. no-continuation  ..
696          (ad-it                   ;No previous "\" and next line exist
697           (end-of-line)
698           (delete-horizontal-space)     ;Clear the EOL
699           ;;  Only if there is no text, T is target, but next line has
700           ;;  longer line.
701           ;;
702           ;;       T
703           ;;  This rule here \
704           (if (<= (current-column) col-target)
705               (ti::buffer-move-to-col col-target))
706           (insert "\\")))
707         (forward-line 1))))
708     (goto-char point)                   ;Restore user position
709     (when verb
710       (cond
711        (kill-it
712         (message "Fix backslash: backslashes removed."))
713        (col-target
714         (message
715          "Fix backslash: backslashes in column %d" col-target))))))
716
717 ;;; ----------------------------------------------------------------------
718 ;;; - in many C/C++ styles the variables are names so that they start
719 ;;;   with lowercase letters and following ones are catenated + first char
720 ;;;   in upcase.
721 ;;; - Function names may start with uppercase.
722 ;;;
723 ;;;
724 (defun ti::buffer-upcase-words-to-variable-names (beg end &optional case-fold)
725   "Does following conversion by searhing caps only words in region.
726
727   THE_COLUMN_NAME  --> theColumnName
728
729 Input:
730
731   BEG END       region bounds
732   CASE-FOLD     the value of `case-fold-search'. nil means that  the
733                 upcase \"words\" are counted only. Non-nil accepts
734                 seearching mixed case words."
735   (interactive "*r\nP")
736   (let* ((case-fold-search      case-fold) ;; case is significant..
737          (ptable                (syntax-table)) ;; previous
738          (table                 (make-syntax-table))
739          f1
740          f2)
741     (save-restriction
742       (unwind-protect
743           (progn
744             (narrow-to-region beg end)
745             (ti::pmin)
746             ;;  let's make sure the _ is not in a word class, put it
747             ;;  into some other class for now.
748
749             (modify-syntax-entry ?_ "_" table)
750             (set-syntax-table table)
751             (while (re-search-forward "[A-Z][A-Z_]+" nil t)
752               (setq beg (match-beginning 0)
753                     end (match-end 0))
754               (save-excursion
755                 (setq f1 (looking-at "[ \t]\\|$"))
756                 (goto-char (1- beg))
757                 (setq f2 (looking-at "[ \t]\\|$")))
758               (cond
759                ((and f1 f2)
760                 ;; make first word "lowercase only"
761                 (goto-char beg)
762                 (downcase-word 1)
763                 ;; handle next words, until space/eol/eob is seen
764                 (while (and (not (eobp))
765                             (not (looking-at "[ \t]\\|$")))
766
767                   ;; Remove that underescore
768                   ;; Capit. command moves forward while doing
769
770                   (and (looking-at "_")
771                        (delete-char 1))
772                   (capitalize-word 1)))))
773             ;; ... ... ... ... ... ... ... ... ... ... ... .. unwind end . .
774             ;;  Now, make sure the old table is restored,
775             ;;  the unwind protects against Ctrl-g
776             (set-syntax-table ptable))))
777     ;; let-defun end
778     nil))
779
780 ;;; ----------------------------------------------------------------------
781 ;;;
782 (defsubst ti::string-nth-from-number (nbr)
783   "Return string representing NBR position: st, nd, th.
784
785 Input:
786   string or number in digit form.
787
788 Return:
789   \"st\", \"nd\", \"th\""
790   (if (stringp nbr)
791       (setq nbr (string-to-int nbr)))
792   (cond
793    ((eq nbr 1) "st")
794    ((eq nbr 2) "nd")
795    ((eq nbr 3) "rd")
796    ((>  nbr 3) "th")
797    (t
798     (error "invalid ARG" nbr))))
799
800 ;;; ----------------------------------------------------------------------
801 ;;; #todo
802 ;;; - Did 19.29+ change the current-time function? Oh my...say no?
803 ;;;   --> should handle it if the format changed.
804 ;;;
805 (defun ti::date-time-elements (&optional zero-form time-string)
806   "Return list of elements derived from `current-time'.
807 This is old function, you should use newer `format-time-string'.
808
809 Input:
810
811   ZERO-FORM     make sure numbers have preceeding zeroes. Like 7 --> 07
812   TIME-STRING   user supplied time string in `current-time' format.
813
814 Return list form: \( dd mm ...\)
815
816   0 dd     nbr,  day                  if zero-form: ti::string-value
817   1 mm     nbr,  month                if zero-form: ti::string-value
818   2 yy     2nbr, year
819   3 tt     2nbr, hh:mm
820   4 wd     3str, week day, string like 'Mon'
821   5 m      str,  month, full string
822   6 yyyy   4str, whole year"
823   (interactive)
824   (let (time m mm dd yy tt wd yyyy)
825     (setq time (or time-string
826                    (current-time-string))) ;"Wed Oct 14 22:21:05 1987"
827     (setq wd (substring time 0 3))
828     (setq m  (substring time 4 7))
829     (setq mm (or (ti::date-month-to-number m) 0))
830     ;;    we remove trailing space  "2 " --> 2 --> "2"
831     (setq dd (string-to-int (substring time 8 10)))
832     (setq tt (substring time -13 -8))
833     (setq yy (substring time -2 nil))
834     (setq yyyy (substring time -4 nil))
835     (cond
836      (zero-form                         ;convert "7" --> "07"
837       (setq dd (int-to-string dd))
838       (setq mm (int-to-string mm))
839       (if (not (eq (length dd) 2))
840           (setq dd (concat "0" dd)))
841       (if (not (eq (length mm) 2))
842           (setq mm (concat "0" mm)))))
843     (list dd mm yy tt wd m yyyy)))
844
845 ;;; ----------------------------------------------------------------------
846 ;;; - This is mainly used, if you read the regexp from the buffer:
847 ;;;   obviously you can't just pick it from there:
848 ;;;
849 ;;;             "find this\t+"
850 ;;;                       ^^
851 ;;;   and use it in re-search-XXX commands. See function  ti::buffer-get-re
852 ;;;   which does the conversion automatically by calling these functions.
853 ;;;
854 (defun ti::string-char-to-escape-char (item)
855   "Converts ITEM to escape sequence \"t\" --> \"\\t\".
856
857 Input:
858
859   item       integer, character, or single string
860
861 Return:
862
863   nil        if cannot identify ITEM.
864   string     escape char"
865   (let* (el ret
866             (table
867              '(("a" . 7)
868                ("b" . 8)
869                ("f" . 12)
870                ("n" . 10)
871                ("r" . 13)
872                ("t" . 9)
873                ("v" . 11))))
874     (if (integerp item)
875         (setq item (char-to-string item)))
876     (if (setq el (assoc item table))
877         (setq ret (char-to-string (cdr el))))
878     ret))
879
880 ;;; ----------------------------------------------------------------------
881 ;;;
882 (defun ti::string-plain-string-to-regexp (str)
883   "Convert slashes in STR \\\ --> \.
884 If you read from buffer two some special characters, it can't be
885 used like that right a way for regexp. E.g. in buffer \\\\ two slashes mean
886 one slash actually when assigned to string to form the regexp."
887   (let* ((ret           "")
888          (i             0)
889          (len           (length str))
890          (look-ch       ?\\)
891          (prev-ch       ?d)             ;just some dummy
892          (count         0)
893          chs
894          ch)
895     (while (< i len)
896       (setq ch      (aref str i)
897             chs     (char-to-string ch))
898       (if (eq ch look-ch)               ;add counter when EQ
899           (incf count))
900       (cond
901        ((eq count 2)                    ;two successive ?
902         (if (eq prev-ch look-ch)
903             (setq count 0)              ;delete second
904           (setq ret (concat ret chs))
905           (setq count 0)))
906        ((eq count 1)
907         (if (eq ch look-ch)
908             ;;  Right now it was found
909             (setq ret (concat ret chs))
910           ;; - Count is still 9, but we aren't looking at double \\ ?
911           ;;   --> there is \t sequence
912           ;; - we revove last char and put our sequence instead
913           (setq ret (concat
914                      (substring ret 0 (1- (length ret)))
915                      (ti::string-char-to-escape-char chs)))
916           (setq count 0)))
917        (t
918         (setq ret (concat ret chs))))
919       (setq prev-ch ch )
920       (incf i))
921     ret))
922
923 ;;; ----------------------------------------------------------------------
924 ;;; arc.mode.el  -- This is from 19.28 distrib.
925 ;;;
926 (defun ti::file-access-mode-to-string (mode)
927   "Turn an integer MODE, 0700 (i.e., 448) into a mode string like -rwx------."
928   (let ((str (make-string 10 ?-)))
929     (or (zerop (logand 16384 mode)) (aset str 0 ?d))
930     (or (zerop (logand  8192 mode)) (aset str 0 ?c)) ; completeness
931     (or (zerop (logand   256 mode)) (aset str 1 ?r))
932     (or (zerop (logand   128 mode)) (aset str 2 ?w))
933     (or (zerop (logand    64 mode)) (aset str 3 ?x))
934     (or (zerop (logand    32 mode)) (aset str 4 ?r))
935     (or (zerop (logand    16 mode)) (aset str 5 ?w))
936     (or (zerop (logand     8 mode)) (aset str 6 ?x))
937     (or (zerop (logand     4 mode)) (aset str 7 ?r))
938     (or (zerop (logand     2 mode)) (aset str 8 ?w))
939     (or (zerop (logand     1 mode)) (aset str 9 ?x))
940     (or (zerop (logand  1024 mode)) (aset str 3 (if (zerop (logand 64 mode))
941                                                     ?S ?s)))
942     (or (zerop (logand  2048 mode)) (aset str 6 (if (zerop (logand  8 mode))
943                                                     ?S ?s)))
944     str))
945
946 ;;; ----------------------------------------------------------------------
947 ;;; See also convert-standard-filename which e.g. changes forward slashes
948 ;;; to backward slashes under win32.
949 ;;;
950 (defun ti::file-name-for-correct-system (path system)
951   "Convert PATH to correct system: 'emacs, 'dos or 'cygwin.
952
953 Input:
954
955 PATH        Path name. This must already be in expanded form.
956             Use Emacs function `expand-file-name' as needed.
957
958 SYSTEM      'cygwin => convert to cygwin path notation
959             'dos    => convert to DOS notation.
960             'emacs  => convert to notation which current Emacs uses.
961                        If running Win32 native Emacs, convert to DOS.
962                        If running Cygwin Emacs, convert to cygwin.
963
964 Notes:
965
966   In native Win32 Emacs, the choice 'emacs work correctly only if package
967   cygwin-mount.el is active. The cygwin path are handled by it."
968   (when (stringp path)
969     (when (string-match "~\\|\\.\\." path) ;; Need absolute path
970       (setq path (expand-file-name path)))
971     (cond
972      ((eq system 'emacs)
973       (setq path (w32-expand-file-name-for-emacs path))
974       (let ((func 'cygwin-mount-substitute-longest-mount-name))
975         (when (and (ti::emacs-type-win32-p)
976                    path
977                    (and (string-match "^/" path))
978                    (fboundp func))
979           ;;  Need to convert Cygwin => DOS path
980           (setq path (funcall func path)))))
981      ((eq system 'cygwin)
982       (setq path (w32-expand-file-name-for-cygwin path)))
983      ((eq system 'dos)
984       (if (string-match "^/cygdrive/" path)
985           (setq path (w32-cygwin-path-to-dos path))))))
986   path)
987
988 ;;}}}
989
990 ;;{{{ Version control, RCS delta files
991
992 ;;; ....................................................... &rcs-delta ...
993 ;;; In general, do not use these function, but use the top-level ones
994 ;;; that deal with filenames or buffers.
995
996 ;;; ----------------------------------------------------------------------
997 ;;;
998 (defsubst ti::vc-rcs-delta-get-revisions  (&optional buffer)
999   "Parse all revision numbers from delta file BUFFER.
1000
1001 Return:
1002   '(version version ..)"
1003   (let* (list)
1004     (save-excursion
1005       (if buffer
1006           (set-buffer buffer))
1007       (ti::pmin)
1008       (while (re-search-forward "^[0-9.]+[0-9]+$" nil t)
1009         (push (match-string 0) list)))
1010     ;; preserve order
1011     (nreverse list)))
1012
1013 ;;; ----------------------------------------------------------------------
1014 ;;;
1015 (defun ti::vc-rcs-delta-get-file (file buffer)
1016   "Read delta FILE to temporary BUFFER.
1017 The delta file is truncated to header info only.
1018
1019 Input:
1020
1021  FILE       RCS file
1022  BUFFER     Existing buffer where to put delta.
1023
1024 Errors:
1025
1026   VC Generates error if file is not vc registered.
1027
1028 Return:
1029
1030   buffer  Possibly newly created buffer."
1031   (let* ((rcs-name   (vc-name file))) ;; CVS returns entries.
1032     (if (or rcs-name
1033             (error "Not an RCS file. %s" file))
1034         (with-current-buffer buffer
1035           (erase-buffer)
1036           (if (fboundp 'vc-insert-file) ;19.30
1037               (ti::funcall 'vc-insert-file rcs-name "^desc")
1038             (insert-file-contents rcs-name)
1039             (buffer-disable-undo)
1040             (set-buffer-modified-p nil)
1041             (auto-save-mode nil)
1042             (if (re-search-forward "^desc" nil t)
1043                 (delete-region (point) (point-max))))))
1044     buffer))
1045
1046 ;;; ----------------------------------------------------------------------
1047 ;;;
1048 (defun ti::vc-rcs-delta-lock-status (&optional user-name)
1049   "Return lock status by reading the delta buffer.
1050 If USER-NAME is non-nil return locks only for that user.
1051
1052 Return:
1053  ((USER . (VER VER ..))  (U . (V V)) ..)
1054  nil"
1055   (let (user
1056         ver
1057         ret)
1058     (save-excursion
1059       (ti::pmin)
1060       ;; locks
1061       ;;       jaalto:1.13; strict;
1062       ;; comment        @; @;
1063       (when  (re-search-forward "^locks" nil t)
1064         (forward-line 1)
1065         (while (re-search-forward
1066                 "^[ \t]+\\([^:]+\\):\\([^;\n\r]+\\)"
1067                 nil t)
1068           (setq user (ti::remove-properties (match-string 1))
1069                 ver  (ti::remove-properties (match-string 2)))
1070           (if (or (null user-name)
1071                   (ti::string-match-case (regexp-quote user-name) user))
1072               (cond
1073                ((assoc user ret)        ;already a user in list
1074                 (ti::assoc-append-inside 'assoc user ret ver))
1075                (t
1076                 (if (null ret)
1077                     (setq ret (list (cons user (list ver))))
1078                   (push (cons user (list ver)) ret ))))))
1079         (forward-line 1)))
1080     (nreverse ret)))
1081
1082 ;;; ----------------------------------------------------------------------
1083 ;;;
1084 (defsubst ti::vc-rcs-delta-lock-status-user (user)
1085   "Return list of locks for USER.
1086 This is toplevel function to `ti::vc-rcs-delta-lock-status'.
1087 Please use it directly if you want other users information too.
1088 If you only need *one* users information, use this function, because
1089 it hides the lock data structure.
1090
1091 Return:
1092  (VER VER ..)    ,list of version strings.
1093  nil"
1094   ;; this always parses the buffer.
1095   (cdr-safe (assoc user (ti::vc-rcs-delta-lock-status))))
1096
1097 ;;; ----------------------------------------------------------------------
1098 ;;;
1099 (defsubst ti::vc-rcs-delta-highest-version ()
1100   "Return the highest version from delta buffer."
1101   (interactive)
1102   (save-excursion
1103     (ti::pmin)
1104     (if (re-search-forward "head[ \t]+\\([.0-9]+\\)" nil t)
1105         (match-string 1))))
1106
1107 ;;}}}
1108 ;;{{{ Version control, general
1109
1110 ;;; ----------------------------------------------------------------------
1111 ;;;
1112 (defun ti::vc-dir-p (file-or-dir)
1113   "Check if FILE-OR-DIR looks like version controlled.
1114 Return type: 'rcs, 'cvs, 'monotone, 'subversion 'git' 'bzr' 'hg' or 'arch.
1115 Note, the return value is LIST."
1116   (let ((dir (cond
1117               ((file-directory-p file-or-dir)
1118                file-or-dir)
1119               ((or (file-name-directory file-or-dir)
1120                    (let ((buffer (or (get-buffer file-or-dir)
1121                                      (get-file-buffer file-or-dir)
1122                                      (find-buffer-visiting file-or-dir))))
1123                      (and buffer
1124                           (file-name-directory
1125                            (buffer-file-name buffer))))))))
1126         (check '(("CVS/Entries" cvs)
1127                  (".svn"    subversion)
1128                  ;; #todo: Correct these
1129                  (".git"   git)
1130                  (".hg"   hg)
1131                  (".bzr" bzr)
1132                  ("MT"   monotone)
1133                  ("arch" arch)))
1134         ret)
1135     (setq dir (file-name-as-directory dir))
1136     (dolist (elt check)
1137       (multiple-value-bind (try type) elt
1138         (setq try (concat dir try))
1139         (if (or (file-exists-p try)
1140                 (file-directory-p try))
1141             (push type ret))))
1142     ret))
1143
1144 ;;}}}
1145 ;;{{{ Version control, string, RCS information
1146 ;;; ............................................................. &rcs ...
1147 ;;; Refer to GNU RCS ident(1) how to construct valid identifiers.
1148
1149 ;;; ----------------------------------------------------------------------
1150 ;;;
1151 (defsubst ti::vc-rcs-read-val (str)
1152   "Cleans the RCS identifiers from the STR and return the value."
1153   (let* ((re ".*[$][^ \t]+: \\(.*\\) [$]"))
1154     (if (and (stringp str)
1155              (string-match re str))
1156         (match-string 1 str)
1157       nil)))
1158
1159 ;;; ----------------------------------------------------------------------
1160 ;;;
1161 (defun ti::vc-rcs-look-id (str)
1162   "Return the RCS identifier in STR."
1163   (let* ((re ".*[$]\\([^ \t]+\\): .* [$]"))
1164     (if (string-match re str)
1165         (match-string 1 str)
1166       nil)))
1167
1168 ;;}}}
1169 ;;{{{ Version control, CVS
1170
1171 ;;; ----------------------------------------------------------------------
1172 ;;;
1173 (defsubst ti::vc-cvs-to-cvs-dir (file)
1174   "Return CVS directory for file."
1175   (concat (file-name-directory file) "CVS"))
1176
1177 ;;; ----------------------------------------------------------------------
1178 ;;;
1179 (defsubst ti::vc-cvs-to-cvs-dir-p (file)
1180   "Check if there is CVS directory for file. Return CVS path if CVS exist."
1181   (let* ((path (ti::vc-cvs-to-cvs-dir file)))
1182     (when (file-directory-p path)
1183       path)))
1184
1185 ;;; ----------------------------------------------------------------------
1186 ;;;
1187 (defun ti::vc-cvs-to-cvs-file (file cvs-file)
1188   "Use FILE or directory and return CVS/CVS-FILE, like `Root'.
1189 If CVS-FILE does not exist, return nil."
1190   (let* ((path (ti::vc-cvs-to-cvs-dir file))
1191          (root (and path (concat path "/" cvs-file))))
1192     (when (and root
1193                (file-exists-p root))
1194       root)))
1195
1196 ;;; ----------------------------------------------------------------------
1197 ;;;
1198 (defun ti::vc-cvs-to-cvs-file-content (file cvs-file)
1199   "Use FILE or directory name as base and return contents of CVS-FILE as string."
1200   (let* ((file (ti::vc-cvs-to-cvs-file file cvs-file)))
1201     (when file
1202       (with-temp-buffer
1203         (insert-file-contents file)
1204         (buffer-string)))))
1205
1206 ;;; ----------------------------------------------------------------------
1207 ;;;
1208 (defun ti::vc-cvs-file-exists-p (file)
1209   "Return cvs-entry if FILE is in VCS controlled.
1210 Look into CVS/Entries and return line from it if file was CVS controlled."
1211   (let* ((cvs-dir (ti::vc-cvs-to-cvs-dir-p file))
1212          cvs-file)
1213     (when (and cvs-dir
1214                (file-directory-p cvs-dir)
1215                (setq cvs-file (concat cvs-dir "/Entries"))
1216                (file-exists-p cvs-file))
1217       (with-temp-buffer
1218         ;;  CVS/Entries contain information on files in repository
1219         (ti::find-file-literally cvs-file (current-buffer))
1220         ;; /tinylib.el/1.1.1.1/Thu Dec 24 04:34:10 1998//
1221         (if (re-search-forward
1222              (concat "^/" (regexp-quote (file-name-nondirectory file)))
1223              nil t)
1224             (ti::read-current-line))))))
1225
1226 ;;; ----------------------------------------------------------------------
1227 ;;;
1228 (defsubst ti::vc-cvs-entry-split (line)
1229   "Split cvs /Entries LINE into pieces.
1230 /add-log.el/1.1.1.2.2.4/Wed Jan 05 11:25:14 2000//Tb20_4
1231 D/calendar////"
1232   (when line
1233     (split-string line "/")))
1234
1235 ;;; ----------------------------------------------------------------------
1236 ;;;
1237 (defsubst ti::vc-cvs-entry-type (line)
1238   "Return type 'dir or 'file for cvs /Entries LINE"
1239   (when line
1240     (cond
1241      ((string-match "^D/" line) 'dir)
1242      ((string-match "^/"  line) 'file) )))
1243
1244 ;;; ----------------------------------------------------------------------
1245 ;;;
1246 (defsubst ti::vc-cvs-entry-split-info (info what)
1247   "Request information on the CVS Entries line INFO.
1248 Input:
1249
1250   INFO  list returned by `ti::vc-cvs-entry-split'
1251   WHAT  list of returned values: 'file 'revision 'time 'rest."
1252   (let* (ret)
1253     (dolist (type (ti::list-make what))
1254       (push (cond
1255              ((eq type 'file)     (nth 0 info))
1256              ((eq type 'revision) (nth 1 info))
1257              ((eq type 'time)     (nth 2 info))
1258              ((eq type 'rest)     (nth 4 info))
1259              ((error "Invalid WHAT arg %s" type)))
1260             ret))
1261     ;; preserve order.
1262     (nreverse ret)))
1263
1264 ;;}}}
1265 ;;{{{ Version control, RCS
1266
1267 ;;; ----------------------------------------------------------------------
1268 ;;;
1269 (defsubst ti::vc-rcs-file-p (file)
1270   "Return t if FILE STRING is in RCS controlled form.
1271 That is, if FILE has ,v at the end."
1272   (and (> (length file) 2)
1273        (string= (substring file -2) ",v")))
1274
1275 ;;; ----------------------------------------------------------------------
1276 ;;;
1277 (defun ti::vc-rcs-make-filename (file &optional vc-subdir)
1278   "Constructs RCS controlled FILE name. VC-SUBDIR is by default RCS/.
1279 FILE --> PATH/vc-subdir/FILE,v"
1280   (let* (ret
1281          fn
1282          dir)
1283     (cond
1284      ((ti::vc-rcs-file-p file)
1285       (setq ret file))
1286      (t
1287       (setq dir (or (file-name-nondirectory file) "./"))
1288       (setq fn  (file-name-directory file))
1289       (setq ret (concat dir (or vc-subdir "RCS/") fn ",v"))))
1290     ret))
1291
1292 ;;; ----------------------------------------------------------------------
1293 ;;;
1294 (defsubst ti::vc-rcs-file-exists-p (file)
1295   "Return t if equivalent RCS FILE can be found.
1296 If the following condition is met, then such file exists:
1297   ~/dir1/dir2/file.cc     --> ~/dir1/dir2/RCS/file.cc,v"
1298   (let* ((rcs (ti::vc-rcs-make-filename file)))
1299     (file-exists-p rcs)))
1300
1301 ;;; ----------------------------------------------------------------------
1302 ;;;
1303 (defsubst ti::vc-rcs-normal-file (rcs-file)
1304   "Return normal file when version controlled RCS-FILE is given."
1305   (let* (( case-fold-search nil))
1306     (when (ti::vc-rcs-file-p rcs-file)
1307       (setq rcs-file (replace-regexp-in-string "RCS/" "" rcs-file))
1308       (setq rcs-file (replace-regexp-in-string ",v"  "" rcs-file)))
1309     rcs-file))
1310
1311 ;;; ----------------------------------------------------------------------
1312 ;;;
1313 (defun ti::vc-rcs-sort-same-level-list (list)
1314   "Sort RCS revision LIST, which are at same level.
1315 Ie. when only the last version number changes:
1316 1.1 1.2 1.3, or 1.2.1.1 1.2.1.3 1.2.1.10"
1317   (let* ((max 0)
1318          ptr
1319          new-list
1320          len
1321          ret
1322          padd
1323          str)
1324     ;; ... ... ... ... ... ... ... ... ... ... ... ... ... .. greatest ...
1325     (dolist (nbr list)                  ;find greatest. 1.xx
1326       (setq max (max (length nbr) max)))
1327     ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... .. padd ...
1328     (setq ptr list)
1329     (dolist (elt ptr)                   ;padd 1.1 --> 1.01
1330       (setq len (length elt))
1331       (unless (eq len max)
1332         (setq padd (make-string (- max len) ?0))
1333         (if (not (string-match "[0-9]+$" elt))
1334             (setq elt nil)              ;Invalid entry
1335           (setq str (match-string 0 elt) )
1336           (setq elt (ti::replace-match 0 (concat padd str) elt))))
1337       (if elt
1338           (push elt new-list)))
1339     ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... .. sort ...
1340     (setq new-list (sort new-list 'string<))
1341     ;; Check if the values are doubled, and only then fix the list.
1342     ;; Hmm, if this happens, then the error is not in the 'sort'
1343     ;; but somewhere else.
1344 ;;;    (cond
1345 ;;;     ((and new-list (string= (nth 0 new-list)
1346 ;;;                          (nth 1 new-list)))
1347 ;;;      (setq new-list (ti::list-remove-successive new-list 'string=))
1348 ;;;      ))
1349     ;; ... ... ... ... ... ... ... ... ... ... ... ... ... remove padd ...
1350     (setq ptr new-list)
1351     (dolist (elt ptr)                   ;fix 1.01 > 1.1
1352       (when (string-match "\\.\\(0+\\)[1-9][0-9]*$" elt)
1353         (setq elt (ti::replace-match 1 "" elt)))
1354       (push elt ret))
1355     (nreverse ret)))
1356
1357 ;;; ----------------------------------------------------------------------
1358 ;;;
1359 (defun ti::vc-rcs-files-in-dir (&optional dir re)
1360   "Return all RCS controlled files in directory DIR.
1361 It doesn't matter if the directory points to RCS itself or
1362 one level up. Thus the two DIR parameters are identical:
1363
1364     /mydir/             takes longer to execute.
1365     /mydir/RCS/
1366
1367 The DIR parameter can hold filename, but only the directory portion
1368 is used. If no directory portion exists \"./\" will be used.
1369
1370 Filenames returned do not have any  \",v\" extensions or directories.
1371
1372 Optional RE tells to return files matching RE only.
1373
1374 Return:
1375  list           (file file ..)"
1376   (let* ((re (or re "."))               ;default to match all
1377          d
1378          fn
1379          fnn
1380          list
1381          ret)
1382     (if (null (file-directory-p dir))
1383         (error "Not a directory"))
1384     (setq d (or (and dir
1385                      (or (file-name-directory (expand-file-name dir))
1386                          "./"))
1387                 "./"))
1388     (cond
1389      ((ti::string-match-case "RCS/?" d)
1390       (setq list (directory-files d nil re))
1391       (dolist (elt list)
1392         (set fn (replace-regexp-in-string ",v$" "" elt))
1393         (push fn ret)))
1394      (t
1395       (setq list (directory-files d nil re))
1396       (dolist (fn list)
1397         (setq fnn (concat d fn))        ;with directory
1398         (if (and (not (file-directory-p fnn))
1399                  (ti::vc-rcs-file-exists-p (concat d fn)))
1400             (push fn ret)))))
1401     ret))
1402
1403 ;;; ----------------------------------------------------------------------
1404 ;;; - The vc. does not return the _version_ latest.
1405 ;;;   See vc-hook/ vc-fetch-properties
1406 ;;;
1407 (defsubst ti::vc-rcs-head-version  (file)
1408   "Get latest version, the head, for FILE.
1409 No errors generates although file is not in RCS tree.
1410
1411 Return:
1412   string    version string
1413   nil       not an rcs file"
1414   (with-temp-buffer
1415     ;;  May not be RCS file
1416     (ignore-errors (ti::vc-rcs-delta-get-file file (current-buffer)))
1417     (ti::vc-rcs-delta-highest-version)))
1418
1419 ;;; ----------------------------------------------------------------------
1420 ;;;
1421 (defun ti::vc-rcs-guess-buffer-version  (file &optional user)
1422   "Try to guess right version number for buffer visiting FILE.
1423 If file is locked, look at delta log to find out version, otherwise call
1424 `ti::vc-rcs-buffer-version' and consult vc if needed.
1425
1426 Input:
1427
1428   FILE      file name
1429   USER      rcs user name, defaults to (user-login-name)
1430
1431 Return:
1432
1433   string
1434   nil"
1435   (let* ((user (or user (user-login-name)))
1436          list
1437          ver)
1438     (when (not buffer-read-only)        ;It's Checked Out
1439       ;; Never trust the ID string in the buffer, always look
1440       ;; at delta file --> this may be checked out with -k and
1441       ;; then RCS strings are not updated.
1442       (with-temp-buffer
1443         (ti::vc-rcs-delta-get-file file (current-buffer))
1444         ;; We're interested in current user's locks only
1445         (setq list (ti::vc-rcs-delta-lock-status user))))
1446     (cond
1447      ((and list
1448            (eq 1 (length list))
1449            (setq list (cdr (car list)))
1450            (eq 1 (length list)))
1451       ;; Okay, only 1 version locked, then we're safe
1452       (setq ver (car list)))
1453      (t
1454       (setq ver
1455             (or (save-excursion
1456                   (set-buffer (get-file-buffer file))
1457                   (ti::vc-rcs-buffer-version))
1458                 (vc-file-getprop file 'vc-workfile-version)
1459                 nil))))
1460     ver))
1461
1462 ;;; ----------------------------------------------------------------------
1463 ;;;
1464 (defun ti::vc-rcs-buffer-version (&optional buffer)
1465   "Return version number for optional BUFFER.
1466 Supposes that RCS string 'Revision' 'Id' or 'Log' exist.
1467 If they do not exist, then see if VC is loaded and look at the modeline.
1468
1469 Please use `ti::vc-rcs-guess-buffer-version' and not this function."
1470   (let* (rev
1471          tmp)
1472     (save-excursion
1473       (if buffer
1474           (set-buffer buffer))
1475       (ti::widen-safe
1476         (ti::pmin)
1477         (cond
1478          ((setq tmp (ti::vc-rcs-str-find "Revision"))
1479           (setq rev (ti::vc-rcs-read-val tmp)))
1480          ((ti::vc-rcs-str-find "Log" )
1481           (forward-line)
1482           (setq rev (ti::buffer-match ".*Revision +\\([0-9.]+\\)" 1)))
1483          ((setq tmp (ti::vc-rcs-str-find "Id" 'value))
1484           (setq rev (nth 1 (split-string tmp " ")))))))
1485     ;;  See if VC is installed and ask from it then.
1486     (if (and (null rev)
1487              (fboundp 'vc-mode-line))
1488         (setq rev (ti::string-match  "[^.0-9]*\\([.0-9]+\\)" 1
1489                                      (or (symbol-value 'vc-mode) ""))))
1490     rev))
1491
1492 ;;; ----------------------------------------------------------------------
1493 ;;;
1494 (defsubst ti::vc-rcs-rlog-get-revisions ()
1495   "REad all revision numbers from rcs rlog buffer.
1496 The line searched looks like:
1497
1498    revision 1.10   locked by: loginName;
1499    revision 1.9
1500
1501 Return:
1502
1503   list    revision numbers
1504   nil"
1505   (let* ((re   "^revision[ \t]+\\([.0-9]+\\)$")
1506          ver
1507          list)
1508     (save-excursion
1509       (ti::pmin)
1510       (while (re-search-forward re nil t)
1511         (if (setq ver (match-string 1))
1512             (push ver list))))
1513     (nreverse list)))
1514
1515 ;;; ----------------------------------------------------------------------
1516 ;;;
1517 (defsubst ti::vc-rcs-all-versions  (file)
1518   "Return string list of all version numbers for FILE."
1519   (with-temp-buffer
1520     (ti::vc-rcs-delta-get-file file (current-buffer))
1521     (ti::vc-rcs-delta-get-revisions)))
1522
1523 ;;; ----------------------------------------------------------------------
1524 ;;; For big files this is real slow, since building up lists and
1525 ;;; sort the revisions is hard
1526 ;;;
1527 (defun ti::vc-rcs-previous-version (version v-list)
1528   "Return previous version for FILE.
1529 Do not call this function Often, since it may be quite time consuming.
1530
1531 Input:
1532
1533   VERSION       ,lever as string, e.g. \"1.5\"
1534   V-LIST        ,all version numbers for file, order not significant.
1535
1536 Return:
1537
1538   RCS tree      previous version
1539   1.5           1.4
1540   1.4           1.3
1541   1.3           1,2
1542     1.3.1.1     1.3
1543     1.3.1.2     1.3.1.1
1544   1.2           1.1
1545   1.1           nil"
1546   (let* (branch-list
1547          list
1548          tmp
1549          ret)
1550     (setq branch-list   (ti::vc-rcs-get-all-branches version v-list))
1551     (cond
1552      ((null branch-list)
1553       ;; record the error to *Message* buffer
1554       (message "Tinylib: [rcs] This level does not have version? %s" version))
1555      ;; after 1.1.1.1 we go up one level, to 1.1
1556      ((setq ret (ti::string-match"\\([.0-9]*\\).1.1$" 1  version)))
1557      (t
1558       (setq list branch-list    tmp nil)
1559       (dolist (elt list)
1560         (if (not (string= elt version))
1561             (setq tmp elt)
1562           (setq ret tmp)
1563           (return)))))
1564     ret))
1565
1566 ;;; ----------------------------------------------------------------------
1567 ;;;
1568 (defun ti::vc-rcs-get-all-branches (rev rev-list)
1569   "Return sorted braches, lowest first, at same revion level.
1570
1571 Input:
1572
1573   REV           version number string
1574   REV-LIST      list of version numbver string
1575
1576 Example:
1577
1578   if version is 1.2,     return all 1.x     branches
1579   if version is 1.2.1.1, return all 1.2.1.x branches"
1580   (let* (list
1581          val)
1582     (if (null val)                      ;Quiet XEmacs 19.14 ByteComp
1583         (setq val (ti::string-match ".*\\." 0 rev))) ;remove last number
1584     (setq
1585      list
1586      (ti::list-find rev-list
1587                     rev
1588                     ;;  - The count thing just makes sure we get
1589                     ;;    1.1  and 1.2  , not 1.1.1.1
1590                     ;;  - match makes sure that the start of the string is same
1591                     ;;    1.  --> 1.2 1.3 1.4
1592                     (function
1593                      (lambda (arg elt)
1594                        (and (eq (count-char-in-string ?. arg)
1595                                 (count-char-in-string ?. elt))
1596                             (string-match val elt))))
1597                     'all-matches))
1598     (when list
1599       ;; Simple (setq list (sort list 'string<)) won't do the job,
1600       ;; since it claims 1.10 is before 1.9
1601       ;;
1602       ;; 1.1
1603       ;; 1.10           ;; see ?
1604       ;; 1.2
1605       ;; 1.9
1606       (setq list (ti::vc-rcs-sort-same-level-list list)))
1607     list))
1608
1609 ;;}}}
1610 ;;{{{ Version control, buffer's RCS strings, other
1611
1612 ;;; ----------------------------------------------------------------------
1613 ;;;
1614 (defun ti::vc-version-string-p (version)
1615   "Test if VERSION looks like version number N.N, N.N.N etc."
1616   (and (stringp version)
1617        (string-match "^[0-9]+\\.[0-9]+\\(\\.[0-9]+\\)*$" version)))
1618
1619 ;;; ----------------------------------------------------------------------
1620 ;;;
1621 (defun ti::vc-version-simple-p (version)
1622   "test if VERSION is simple N.N; N.N.N would be complex."
1623   (and (stringp version)
1624        (eq 1 (count-char-in-string ?. version))))
1625
1626 ;;; ----------------------------------------------------------------------
1627 ;;;
1628 (defun ti::vc-version-lessp (a b &optional zero-treat)
1629   "Return t if A is later version than B.
1630 This function can only check only three levels; up till: NN.NN.NN.
1631
1632 Examples:
1633
1634   2     > 1.1
1635   1.11  > 1.3
1636   1.3.1 > 1.1
1637
1638 Input
1639
1640   A             Version string one
1641   B             Version string two
1642   ZERO-TREAT    If non-nil, consider version numbers starting with 0.NN
1643                 never than 2.1. In this case it is assumed
1644                 that zero based versions are latest development releases."
1645   (flet ((version (str regexp)
1646                   (if (string-match regexp str)
1647                       (string-to-number (match-string 1 str))
1648                     0)))
1649     (let* ((a1 (version a "^\\([0-9]+\\)"))
1650            (a2 (version a "^[0-9]+\\.\\([0-9]+\\)"))
1651            (a3 (version a "^[0-9]+\\.[0-9]+\\.\\([0-9]+\\)"))
1652            (b1 (version b "^\\([0-9]+\\)"))
1653            (b2 (version b "^[0-9]+\\.\\([0-9]+\\)"))
1654            (b3 (version b "^[0-9]+\\.[0-9]+\\.\\([0-9]+\\)")))
1655       (or (and zero-treat
1656                (and (= a1 0)
1657                     (> b1 0)))
1658           (> a1 b1)
1659           (and (= a1 b1)
1660                (> a2 b2))
1661           (and (= a1 b1)
1662                (= a2 b2)
1663                (> a3 b3))))))
1664
1665 ;;; ----------------------------------------------------------------------
1666 ;;;
1667 (defun ti::vc-rcs-str-find (str &optional mode)
1668   "Try to find rcs string STR starting from the point forward.
1669
1670 Return:
1671
1672     By default whole string is returned.
1673     If MODE is non-nil, the value of rcs identifier is returned."
1674
1675   ;;  RCS keywords are like this:
1676   ;;
1677   ;;        $ Revision:
1678
1679   (let* ((re (concat "[$]" str ":[^$]+[$]"))
1680          ret)
1681     (if (null (re-search-forward re nil t))
1682         nil
1683       (setq ret (match-string 0))
1684       (if (null mode)
1685           ret
1686         (ti::vc-rcs-read-val ret)))))
1687
1688 ;;; ----------------------------------------------------------------------
1689 ;;; - In fact this should be macro, defsubst
1690 ;;;
1691 (defsubst ti::vc-rcs-str-find-buffer (str &optional mode)
1692   "Try to find rcs string STR starting from `point-min'.
1693 Return:
1694
1695     By default whole string is returned.
1696     If MODE is non-nil, the value of rcs identifier is returned.
1697
1698 Example call:
1699
1700   (ti::vc-rcs-str-find-buffer \"Id\" 'value)"
1701   (save-excursion
1702     (ti::widen-safe
1703       (ti::pmin)
1704       (ti::vc-rcs-str-find str mode))))
1705
1706 ;;}}}
1707
1708 ;;{{{ Date
1709
1710 ;;; ............................................................ &date ...
1711
1712 ;;; ----------------------------------------------------------------------
1713 ;;;
1714 (defun ti::date-standard-rfc-regexp (&optional type time)
1715   "Return RFC date matching regexp: Feb  9 16:50:01.
1716 Input:
1717
1718   TYPE   \"mon\"  .. \"mon-date-hh-mm-ss\" What elements to inlcude.
1719   TIME   if not set, use `current-time'.
1720
1721 Note it makes no sense to request \"mon-mm\", because the return
1722 value si cumulated. Do not leave out directived from the middle, but
1723 tag in order:
1724
1725   mon
1726   mon-date
1727   mon-date-hh
1728   mon-date-hh-mm
1729   mon-date-hh-mm-ss."
1730   (or time
1731       (setq time (current-time)))
1732   (let* ((mon  (format-time-string "%b" time))
1733          (dd   (ti::string-trim-blanks
1734                 (format-time-string "%e" time)))
1735          (hh   (format-time-string "%H" time))
1736          (mm   (format-time-string "%M" time))
1737          (ss   (format-time-string "%S" time))
1738          ret)
1739     (cond
1740      ((not (stringp type))
1741       nil)
1742      (t
1743       (when (string-match "mon" type)
1744         (setq ret (concat (or ret "") mon)))
1745       (when (string-match "date" type)
1746         (setq ret (concat (or ret) " +" dd)))
1747       (when (string-match "hh" type)
1748         (setq ret (concat (or ret) " +" hh)))
1749       (when (string-match "mm" type)
1750         (setq ret (concat (or ret) ":" mm)))
1751       (when (string-match "ss" type)
1752         (setq ret (concat (or ret) ":" ss)))))
1753     ret))
1754
1755 ;;; ----------------------------------------------------------------------
1756 ;;; #defalias  (defalias 'time-now 'ti::date-standard-date)
1757 ;;;
1758
1759 (when (fboundp 'format-time-string)     ;19.29+
1760   (defun ti::date-standard-date (&optional type time)
1761     "Return time RFC 'Nov 07 1995 20:49' or in SHORT
1762 Input:
1763   TYPE  return YYYY-MM-DD instead (ISO 8601).
1764         if 'minutes, return YYYY-MM-DD HH:MM.
1765   TIME-STRING   User supplied string in format `current-time-string'."
1766     (cond
1767      ((eq 'minutes type)
1768       (format-time-string "%Y-%m-%d %H:%M" (or  time (current-time))))
1769      (type
1770       (format-time-string "%Y-%m-%d" (or  time (current-time))))
1771      (t
1772       (format-time-string "%b %d %Y %H:%M"
1773                           (or  time (current-time)))))))
1774
1775 ;;; ---
1776 (unless (fboundp 'format-time-string)
1777   (defun ti::date-standard-date (&optional type time)
1778     "Return Time 'Nov 10th 1995 20:49'.
1779 Input:
1780   TYPE  return YYYY-MM-DD ISO 8601.
1781         if 'minutes, return YYYY-MM-DD HH:MM.
1782   TIME  User supplied time in format `current-time'."
1783     (interactive "P")
1784     (let* ((list  (ti::date-time-elements nil (current-time-string
1785                                                (or time (current-time)))))
1786            nbr)
1787       (cond
1788        (type
1789         (setq nbr (cdr (assoc (nth 5 list) (ti::month-mm-alist))))
1790         (concat
1791          (nth 6 list) "-"
1792          (int-to-string nbr)
1793          "-"
1794          (int-to-string (nth 0 list))
1795          (if (not (eq type 'minutes))
1796              ""
1797            (concat " " (nth 3 list)))))
1798        (t
1799         (concat (nth 5 list) " "
1800                 (int-to-string (nth 0 list))
1801                 (ti::string-nth-from-number  (nth 0 list)) " "
1802                 (nth 6 list) " "
1803                 (nth 3 list)))))))
1804
1805 ;;; ----------------------------------------------------------------------
1806 ;;;
1807 (defun ti::date-month-to-number (arg &optional mode)
1808   "Return month number for string or vice versa.
1809
1810 When MODE is nil
1811
1812   Accepts Jan or January with any case     --> Return nbr or nil
1813
1814 When MODE is non-nil
1815
1816   Accepts nbr or str-nbr                   --> return str or nil"
1817   ;; (interactive)
1818   (let ((alist
1819          '(("jan" . 1)    ("feb" . 2)     ("mar" . 3)     ("apr" . 4)
1820            ("may" . 5)     ("jun" . 6)     ("jul" . 7)     ("aug" . 8)
1821            ("sep" . 9)     ("oct" . 10)    ("nov" . 11)    ("dec" . 12)))
1822         len
1823         idx
1824         el
1825         ret
1826         str)
1827     (cond
1828      ((eq nil mode)
1829       (setq len (length arg))
1830       (if (> len 3) (setq arg (substring str 0 3))) ; cut to 3 chars
1831       (setq idx (downcase arg))
1832       (if (setq el (assoc idx alist))
1833           (setq ret (cdr el))))
1834      (t
1835       (if (stringp arg) (setq arg (string-to-int arg)))
1836       (setq idx arg)
1837       (if (setq el (rassq idx alist))
1838           (setq ret (car el)))))
1839     ret))
1840
1841 ;;; ----------------------------------------------------------------------
1842 ;;;
1843 (defun ti::date-time-difference (a b &optional float)
1844   "Calculate difference beween times A and B optionally in FLOAT seconds.
1845 The input must be in form of '(current-time)'
1846 The returned value is difference in seconds.
1847 E.g. if you want to calculate days; you'd do
1848 \(/ (ti::date-time-difference a b) 86400) ;; 60sec * 60min * 24h"
1849   (if float
1850       (progn
1851         (multiple-value-bind (s0 s1 s2) a
1852           (setq a (+ (* (float (ash 1 16)) s0)
1853                      (float s1) (* 0.0000001 s2))))
1854         (multiple-value-bind (s0 s1 s2) b
1855           (setq b (+ (* (float (ash 1 16)) s0)
1856                      (float s1) (* 0.0000001 s2))))
1857         (- a b))
1858     (let ((hi (- (car a) (car b)))
1859           (lo (- (car (cdr a)) (car (cdr b)))))
1860       (+ (lsh hi 16) lo))))
1861
1862 ;;; ----------------------------------------------------------------------
1863 ;;;
1864 (defun ti::date-time-diff-days  (std1 std2)
1865   "Return approximation of time difference in days.
1866 STD1 and STD2 are two standard times in short format YYYY-MM-DD.
1867 In calculation each month is supposed to have 30 days and a year 356 days."
1868   (let ((re  "\\([0-9][0-9][0-9][0-9]\\)-\\([0-9]+\\)-\\([0-9]+\\)")
1869         y1 m1 d1
1870         y2 m2 d2
1871         ret)
1872     (string-match re std1)
1873     (setq y1 (string-to-int (match-string 1 std1))
1874           m1 (string-to-int (match-string 2 std1))
1875           d1 (string-to-int (match-string 3 std1)))
1876     (string-match re std2)
1877     (setq y2 (string-to-int (match-string 1 std2))
1878           m2 (string-to-int (match-string 2 std2))
1879           d2 (string-to-int (match-string 3 std2)))
1880     (if (>= (- d2 d1) 0)                ;day2 is smaller
1881         (setq ret (- d2 d1))
1882       (setq ret (- (+ 30 d2) d1))
1883       (decf m2))
1884     (incf ret (* 30  (- m2 m1)))
1885     (incf ret (* 356 (- y2 y1)))
1886     ret))
1887
1888 ;;; ----------------------------------------------------------------------
1889 ;;; Try this:  (ti::date-parse-date "Wed, 21 Jul 93 09:26:30 EST")
1890 ;;;
1891 (defun ti::date-parse-date (str)
1892   "Try to parse date field.
1893
1894 Return:
1895
1896   list          ,(dd mm yy tt wd m yy tz)
1897                  \"\" in fields which weren't identified.
1898
1899   list members:
1900   0 YYYY   year         4 numbers
1901   1 mm     month        number
1902   2 dd     day          number
1903   3 tt     hh:mm        nbr:nbr
1904   4 wd     week day     string e.g. \"Mon\"
1905   5 m      month        string e.g. \"Jun\"
1906   7 tz     time zone    e.g. [+-]nnnn, where n = number"
1907   (let* (wd
1908          dd
1909          mm
1910          m
1911          yyyy
1912          tt
1913          tz
1914
1915          (rAaa   "\\([A-Z][a-z][a-z]\\)")
1916          (rd     "\\([0-9][0-9]?\\)")          ;; typical day nbr
1917          (rd4    "\\([0-9][0-9][0-9][0-9]\\)") ;; typical year nbr (regexp day)
1918          (rt     "\\([0-9]+:[0-9:]+\\)")       ;; time
1919          ;; UTC+2  GMT+2
1920          (rz     "\\([+-][0-9]+\\|[A-Z][A-Z][A-Z]+[^ \t\n]*\\)?") ;; timezone
1921          (re-yyyy
1922           (concat rd4 " +" rt)) ;; 1994 08:52:25
1923          (re-yy
1924           (concat rd " +" rt)) ;; 94 08:52:25
1925          (re-wd
1926           (concat rAaa ",? +" rd " +" rAaa)) ;; weekday: Mon, 24 Oct
1927          (re-dd
1928           (concat rd ",? +" rAaa " +")) ;;  24 Oct
1929          ;;  (current-time-string) Wed Oct 14 22:21:05 1987
1930          (re-wd-4y
1931           (concat re-wd " +" re-yyyy " *" rz )) ;; Mon, 24 Oct 1994 08:52:25 +0200
1932          (re-wd-2y
1933           (concat re-wd " +" re-yy " *" rz )) ;; Mon, 24 Oct 94 08:52:25 +0200
1934          (re-dd-yyyy                          ;
1935           (concat re-dd re-yyyy " *" rz)) ; 24 Oct 1994 00:28:04 GMT
1936          (re-dd-yy
1937           ;; 24 Oct 94 00:28:04 GMT
1938           (concat re-dd re-yy " *" rz)))
1939     ;; Tue, 1 Nov 1994 8:52:36 +0300 (EET)
1940     (cond
1941      ((or (string-match re-wd-4y str)
1942           (string-match re-wd-2y str))
1943       (setq wd    (match-string 1 str)
1944             dd    (match-string 2 str)
1945             m       (match-string 3 str)
1946             yyyy  (match-string 4 str)
1947             tt    (match-string 5 str)
1948             tz    (match-string 6 str)))
1949      ;;  24 Oct 1994 00:28:04 GMT
1950      ((or (string-match re-dd-yyyy str)
1951           (string-match re-dd-yy str))
1952       (setq dd    (match-string 1 str)
1953             m     (match-string 2 str)
1954             yyyy  (match-string 3 str)
1955             tt    (match-string 4 str)
1956             tz    (match-string 5 str))))
1957     (when (and yyyy (eq (length yyyy) 2))
1958       (setq yyyy (concat
1959                   (if (string-match "^[789]" yyyy) "19" "20")
1960                   yyyy)))
1961     (when m
1962       (setq mm (format "%02d" (ti::date-month-to-number m))))
1963     (when dd
1964       (setq dd (format "%02d" (string-to-int dd))))
1965     (list yyyy mm dd tt wd m tz)))
1966
1967 ;;}}}
1968 ;;{{{ string(s), chars
1969
1970 ;;; ########################################################## &string ###
1971
1972 ;;; ----------------------------------------------------------------------
1973 ;;; #defalias (defalias 'string-repeat 'ti::string-repeat)
1974 ;;;
1975 (defun ti::string-repeat (count char-or-string)
1976   "Repeat COUNT times CHAR-OR-STRING."
1977   (let* ((i 0)
1978          ret)
1979     (if (characterp char-or-string) ;; XEmacs compatibility needed
1980         (setq char-or-string (char-to-string char-or-string)))
1981
1982     (if (integerp char-or-string)
1983         (setq ret (make-string count char-or-string))
1984       (setq ret "")
1985       (while (< i count)
1986         (setq ret (concat ret char-or-string))
1987         (incf i)))
1988     ret))
1989
1990 ;;; ----------------------------------------------------------------------
1991 ;;;
1992 (defun ti::string-syntax-info (char &optional verb)
1993   "Return brief syntax definition string for CHAR. VERB."
1994   (interactive "cShow syntax of char: ")
1995   (let* ((syntax (char-syntax char ))
1996          (elt    (assq syntax ti::var-syntax-info))
1997          (verb   (or verb (interactive-p)))
1998          ret)
1999     (setq ret
2000           (concat
2001            (char-to-string syntax)
2002            " "
2003            (if elt  (nth 1 elt) "")))
2004     (if verb
2005         (message ret))
2006     ret))
2007
2008 ;;; ----------------------------------------------------------------------
2009 ;;;
2010 (defun ti::string-syntax-kill-double-quote ()
2011   "Kill double quote string syntax class for current buffer.
2012 This is usually useful when you turn on `font-lock' in current
2013 buffer where there won't be equal amount of \" and ' pairs.
2014 Your highlighting will then work as expected after syntaxes are killed."
2015   (interactive)
2016   (let ((table (make-syntax-table)))
2017     (modify-syntax-entry ?\" "_" table)
2018     (set-syntax-table table)))
2019
2020 ;;; ----------------------------------------------------------------------
2021 ;;;
2022 (defun ti::string-tabify (string &optional mode)
2023   "Tabify STRING, or if MODE is non-nil, untabify."
2024   (let* ((indent-tabs-mode t))          ;makes sure tabs are used.
2025     (with-temp-buffer
2026       (insert string)
2027       (if (null mode)
2028           (tabify (point-min) (point-max))
2029         (untabify (point-min) (point-max)))
2030       (buffer-string))))
2031
2032 ;;; ----------------------------------------------------------------------
2033 ;;; - This is slightly different than the next one. Use the one you need.
2034 ;;;
2035 (defun ti::string-match-string-subs (level-list &optional string terminate)
2036   "Return matcg list according to subexpression list LEVEL-LIST.
2037
2038 Supposes that you have already done the matching. If STRING is not
2039 given, the buffer will be used for reading.
2040
2041 If optional TERMINATE is non-nil, terminates if any of the matches return
2042 nil. In this case the return list will be empty signifying that all matches
2043 weren't satisfied.
2044
2045 Input:
2046   level-list    list    e.g.   '(1 0 2)
2047   string        str     e.g.   \"testThis\"
2048
2049 Return:
2050  ( \"str\" nil \"str\" .. )
2051  nil                    ,see TERMINATE"
2052   (let* (ret
2053          str)
2054     (dolist (level level-list)
2055       (setq str (match-string level string))
2056       (if (and terminate (null str))
2057           (progn
2058             (setq ret nil)              ;that's it then...
2059             (return))
2060         (push str ret)))
2061     (nreverse ret)))
2062
2063 ;;; ----------------------------------------------------------------------
2064 ;;;
2065 (defun ti::string-match-string-list (match-list level-list string &optional terminate)
2066   "Return match list list according to subexpressions.
2067
2068 Input:
2069
2070   MATCH-LIST    list    e.g.   '(\"\\(re1\\)\" \"re2\" \"\\(cash\\(re3\\)\\)\"
2071   LEVEL-LIST    list    e.g.   '(1 0 2)
2072   STRING        str     e.g.   \"re1 re2 cashre3\"
2073   TERMINATE     any     e.g.   nil, 'terminate
2074
2075 Supposes that you have already done the matching.
2076
2077 If the match wasn't found in current level, it assign nil to the
2078 corresponding position in return list
2079
2080 If optional TERMINATE is non-nil, terminates if any of the matches return
2081 nil. In this case the return list will be empty signifying that all matches
2082 weren't satisfied.
2083
2084 Return:
2085  ( \"str\" nil \"str\" .. )
2086  nil                    ,see TERMINATE"
2087   (let* (ret
2088          str)
2089     (if (not (eq (length match-list)
2090                  (length level-list)))
2091         (error "List length mismatch."))
2092     (while level-list
2093       (setq str (ti::string-match (car match-list) (car level-list) string))
2094       (if (and terminate (null str))
2095           (setq ret nil   level-list nil) ;that's it then...
2096         (ti::nconc ret str))
2097       (pop level-list)
2098       (pop match-list))
2099     ret))
2100
2101 ;;; ----------------------------------------------------------------------
2102 ;;;
2103 (defun ti::string-case-replace (model str &optional symmetry rest-case)
2104   "Use MODEL and change case of characters in STR.
2105 Preserve case if SYMMETRY is non-nil.
2106
2107 E.g. If your input is:
2108
2109         model:          BARMAN
2110         str  :          Foomanager
2111
2112 and the symmetry is non-nil, you get
2113
2114         output:         FOOMANager
2115
2116 If the model is too short the variable REST-CASE instructs what to do
2117
2118   nil      -->  the rest of the STR will be added \"as is\"
2119   'follow  -->  the rest of the STR are in the same case as last
2120                 char in MODEL
2121   'lower   -->  insert rest as lowercase
2122   'upper   -->  insert rest as uppercase"
2123   (let* ((i         0)
2124          (part      "")
2125          case-fold-search               ;case is important
2126          last
2127          len
2128          ret
2129          ch
2130          ch-model)
2131     (if (null symmetry)
2132         str                             ;don't care
2133       (setq len (min (length str) (length model))
2134             ret "")
2135       ;; ............................................ MODEL characters ...
2136       (while (< i len)
2137         (setq ch-model  (char-to-string (aref model i))
2138               ch        (char-to-string (aref str i)))
2139         (cond
2140          ((string-match "[a-z]" ch-model)
2141           (setq ch (downcase ch)   last 'downcase))
2142          ((string-match "[A-Z]" ch-model)
2143           (setq ch (upcase ch)   last 'upcase))
2144          (t
2145           ;; MODEL has punctuation, choose previous case
2146           (if (eq last 'upcase)
2147               (setq ch (upcase ch))
2148             (setq ch (downcase ch)))))
2149         (setq ret (concat ret ch))
2150         (incf i))
2151       ;; ............................................. REST characters ...
2152       ;;  if MODEL is too short, then determine what to do to the rest
2153       ;;  of the characters theat are left.
2154       (when (< (length model) (length str)) ;Need to guess REST model?
2155         (setq part (substring str len))
2156         (cond
2157          ((eq rest-case 'follow)
2158           (setq ch (char-to-string (aref model (1- len)))) ;read last char
2159           (cond
2160            ((string-match "[a-z]" ch)
2161             (setq part (downcase part)))
2162            ((string-match "[A-Z]" ch)
2163             (setq part (upcase part)))
2164            (t
2165             ;; kast char was punctuation, choose last type
2166             (if (eq last 'upcase)
2167                 (setq part (upcase part))
2168               (setq part (downcase part))))))
2169          ((equal rest-case 'upper)
2170           (setq part (upcase part)))
2171          ((equal rest-case 'lower)
2172           (setq part (downcase part)))))
2173       (setq ret (concat ret part))
2174       ret)))
2175
2176 ;;; ----------------------------------------------------------------------
2177 ;;;
2178 (defun ti::string-index (str char &optional reverse)
2179   "Check STR and first CHAR position 0..nbr.
2180 If REVERSE is non-nil, start searching at the end of string."
2181   (let ((len (length str))
2182         (i   -1))
2183     (cond
2184      (reverse
2185       (while (and (>= (decf len) 0)
2186                   (/= (aref str len) char))) ;check character in string
2187       (if (>= len 0)
2188           len
2189         nil))
2190      (t
2191       (while (and   (< (incf i) len)
2192                     (/= (aref str i) char)))
2193       (if (< i len)
2194           i
2195         nil)))))
2196
2197 ;;; ----------------------------------------------------------------------
2198 ;;;
2199 (defun ti::string-index-substring (str char &optional include right seek-end)
2200   "Return left hand substring from STR maching CHAR.
2201
2202 Input:
2203
2204   INCLUDE   The CHAR itself is included too.
2205   RIGHT     Return right hand portion.
2206   SEEK-END  Search from the end.
2207
2208 Example:
2209
2210     ;; To get only the file part, you'd say
2211
2212     (setq string \"user@site:~/bin/myfile\")
2213     (ti::string-index-substring string ?: nil 'right)
2214
2215     ;; To get last item, separated by |
2216
2217     (setq string \"aa|bb|cc|dd\")
2218     (ti::string-index-substring string ?| nil 'right 'seek-end)
2219
2220 Input:
2221
2222   str           string
2223   char          character to look in string
2224   include       flag, should char be included too?
2225   right         return right side of string
2226   seek-end      start looking the position from the end instead
2227
2228 Return:
2229
2230   str   if ch found
2231   nil   no ch found, or impossible condition. Like if input STR is \":\"
2232         and don't want to include ?: character."
2233
2234   (let (idx
2235         ret)
2236     ;;   common mistakes, prevent it immediately, because
2237     ;;   looking the cause in debuffer may be a bit hairy, due to
2238     ;;   breakout only in ti::string-index
2239
2240     (if (not (and str char))
2241         (error "parameter error %s %s" str char))
2242     (if (null (setq idx (ti::string-index str char seek-end)))
2243         nil
2244       (cond
2245        (right
2246         (setq ret (substring str
2247                              (if include
2248                                  idx
2249                                (1+ idx)))))
2250        (t
2251 ;;;     (ti::d! str include idx)
2252         (setq ret (substring str
2253                              0
2254                              (if include ;; the second parameter
2255                                  (1+ idx )
2256                                idx))))))
2257     (if (ti::nil-p ret)                 ;do not return empty strings
2258         nil
2259       ret)))
2260
2261 ;;; ----------------------------------------------------------------------
2262 ;;;
2263 (defun ti::string-replace-one-space (str)
2264   "Convers all spaces/tabs in STR into one space."
2265   ;; #todo: Would using a temporary buffer + untabify + replace-regexps
2266   ;; be faster?
2267   (let* ((out "")
2268          beg
2269          end)
2270     (while (and (> (length str) 0)
2271                 (string-match "[ \t]+\\|$" str))
2272       (setq beg (match-beginning 0) end (match-end 0))
2273       ;;  Take only 1 space (1+ ..
2274       ;;
2275       ;;  no more spaces ? , the "$" matched ...
2276       (if (eq beg (length str))
2277           (progn
2278             ;;  is the rest of it spaces ?
2279             (if (string-match "[ \t]+$" str) nil
2280               (setq out (concat out str)))
2281             (setq str ""))              ;found empty space
2282         (setq out (concat out (substring str 0 (1+ beg))))
2283         (setq str (substring str end))))
2284     out))
2285
2286 ;;; ----------------------------------------------------------------------
2287 ;;; 17 Aug 1995, gnu.emacs.help, kevinr@ihs.com (Kevin Rodgers)
2288 ;;; - Slightly modified by jaalto
2289 ;;;
2290 (defun ti::string-listify (string &optional sep)
2291   "Look STRING and search SEP [whitespace] and return list of substrings."
2292   (let ((start 0)
2293         (sep (or sep "[^ \f\t\n\r\v]+"))
2294         list)
2295     (while (string-match sep string start)
2296       (setq list
2297             (cons (substring string (match-beginning 0) (match-end 0))
2298                   list))
2299       (setq start (match-end 0)))
2300     (nreverse list)))
2301
2302 ;;}}}
2303 ;;{{{ buffer: line, information, dired
2304
2305 ;;; ........................................................ &ange-ftp ...
2306
2307 ;;; ----------------------------------------------------------------------
2308 ;;;
2309 (defun ti::dired-buffer (dir)
2310   "Return dired buffer for DIR if any."
2311   (setq dir (file-name-as-directory dir)) ;; Dired uses trailing slash
2312   (dolist (buffer (buffer-list))
2313     (when (with-current-buffer buffer
2314             (and (eq major-mode 'dired-mode)
2315                  (string= dired-directory dir)))
2316       (return buffer))))
2317
2318 ;;; ----------------------------------------------------------------------
2319 ;;;
2320 (defsubst ti::buffer-get-ange-buffer-list (&optional regexp)
2321   "Return list of ange-ftp buffers matching optional REGEXP."
2322   (ti::dolist-buffer-list
2323    (and (string-match "internal.*ange" (symbol-name major-mode))
2324         (string-match (or regexp "^[*]ftp") (buffer-name)))
2325    'temp-buffers))
2326
2327 ;;; ----------------------------------------------------------------------
2328 ;;;
2329 (defun ti::buffer-find-ange-buffer (user host)
2330   "Find ange ftp buffer with login USER running under HOST.
2331
2332 Return:
2333
2334   buffer"
2335   (car-safe                             ;may be nil list
2336    (ti::buffer-get-ange-buffer-list
2337     (concat "^[*]ftp +" user "@" host "[*]"))))
2338
2339 ;;; ----------------------------------------------------------------------
2340 ;;;
2341 (defun ti::buffer-find-ange-to-dired-buffer ()
2342   "Find associated dired buffer for current ange-ftp buffer.
2343
2344 Return:
2345
2346  list    list of possible buffers
2347  nil"
2348   (let* ( ;;      Check that we're in ange buffer "*ftp ..."
2349          (name   (ti::string-match "^[*]ftp +\\(.*\\)[*]" 1 (buffer-name))))
2350     (when name
2351       (ti::dolist-buffer-list
2352        (and (eq major-mode 'dired-mode)
2353             (string-match
2354              name (or (symbol-value 'dired-directory) "")))))))
2355
2356 ;;; ........................................................ &uuencode ...
2357
2358 ;;; ----------------------------------------------------------------------
2359 ;;;
2360 (defun ti::buffer-uu-area (&optional data-buffer buffer)
2361   "Find uuencoded region forward.
2362
2363 Input:
2364
2365  DATA-BUFFER    Where to look, defaults to `current-buffer'.
2366  BUFFER         If non-nil, put uuencode data here.
2367
2368 Return:
2369
2370   (beg . end)   list, the uu data area
2371   nil           no uu after point found"
2372   (let* ((case-fold-search  nil)        ;must use case sensitive
2373          (beg-re            "begin[ \t]+[0-9]+[ \t]+.")
2374          (end-re            "end[ \t]*$")
2375          beg end
2376          bol
2377          leading)
2378     (save-excursion
2379       (set-buffer (or data-buffer (current-buffer)))
2380       (and (re-search-forward beg-re nil t)
2381            (setq bol (line-beginning-position))
2382            (setq beg (match-beginning 0))
2383            (re-search-forward end-re nil t)
2384            (setq end (line-end-position))))
2385     (when (and beg end buffer)
2386       ;;  First get the data
2387       (with-current-buffer buffer
2388         (erase-buffer)
2389         (insert-buffer-substring data-buffer bol end)
2390         ;;  Remove possible leadings so that you can extract NEWS
2391         ;;  citated UUdata too
2392         ;;
2393         ;;  >  begin 0 cobol.el.gz
2394         ;;  >  M'XL("!?:;S```V-O8F]L+F5L`*P\:W/;1I*?Q;H?,4'MK@A%8"0GL9PH&Z\B
2395         (if (< (- beg bol) 1)           ;no leading characters.
2396             nil
2397           (setq leading (concat "^" (make-string (- beg bol) ?.)))
2398           (ti::pmin)
2399           (ti::buffer-replace-regexp leading 0 ""))
2400         (ti::pmax)
2401         (insert "\n")))
2402     (if (and beg end)
2403         (cons beg end))))
2404
2405 ;;; ----------------------------------------------------------------------
2406 ;;;
2407 (defun ti::buffer-uu-line-p (&optional string)
2408   "Determines if current line is UUencoded. Optionally check STRING.
2409 The line is considered as an uu line if it has no lowercase chars and has
2410 length more than 50 chars. Any leading spaces and tabs are skipped to find
2411 the UU start [applies to buffer reading only].
2412
2413 Return length of line if it's UU, nil if not."
2414   ;; (interactive)
2415   (let* ((case-fold-search      nil)    ;case is important
2416          (at-least              50)
2417          line
2418          len
2419          ret)
2420     (cond
2421      ((setq line (or string (ti::buffer-read-if-solid)))
2422       (setq len  (length line))
2423       (if (and (not (string-match "[a-z]" line)) ;--> not UU line
2424                (> len  at-least))       ;must be longer than xx chars
2425           (setq ret len))))
2426     ret))
2427
2428 ;;; ----------------------------------------------------------------------
2429 ;;;
2430 (defun ti::buffer-area-bounds (beg end)
2431   "Search area bounds delimited by _strings_ BEG and END.
2432 First searches backward, them forward.
2433
2434 Return:
2435   (beg-point . end-point)
2436   nil"
2437   (condition-case nil
2438       (let (p pp)
2439         (save-excursion
2440           (search-backward beg)
2441           (setq p (point))
2442           (search-forward end)
2443           (setq pp (point)))
2444         (if (< (point) pp) (cons p pp) nil))
2445     (search-failed
2446      nil)))
2447
2448 ;;}}}
2449
2450 ;;; ########################################################## &Buffer ###
2451
2452 ;;{{{ buffer: reading lines, chars
2453
2454 ;;; ----------------------------------------------------------------------
2455 ;;;
2456 (defun ti::buffer-parse-grep-line ()
2457   "Parse grep(1) formatted line. FILE:LINE:<content>.
2458 Return:
2459   '(file line content)."
2460   (let* (file
2461          line
2462          rest)
2463     (save-excursion
2464       (beginning-of-line)
2465       (cond
2466        ((looking-at "^[ \t]*\\([^:\r\n]+\\):\\([0-9]+\\):\\(.*\\)")
2467         ;; file:nbr:<rest>
2468         (setq file (match-string 1)
2469               line (match-string 2)
2470               rest (match-string 3)))
2471        ((looking-at "^[ \t]*\\([a-zA-Z]:[^:\r\n]+\\):\\([0-9]+\\):\\(.*\\)")
2472         ;; d:/home/path/file.txt
2473         (setq file (match-string 1)
2474               line (match-string 2)
2475               rest (match-string 3))))
2476       (when line
2477         (if (string-match "^[0-9]+$" line)
2478             (setq line (string-to-int line))
2479           (setq line nil)))
2480       (when file
2481         (list file line rest)))))
2482
2483 ;;; ----------------------------------------------------------------------
2484 ;;;
2485 (defun ti::buffer-parse-grep-line2 ()
2486   "Parse 'file nbr' format. Return '(file line)."
2487   (save-excursion
2488     (beginning-of-line)
2489     (when
2490         (or (looking-at "^[ \t]*\\([^ \t\n:]+\\)[ \t]+\\([0-9]+\\)[ \t:]+")
2491             (looking-at (concat ".*line[ \t,\n]+\\([0-9]+\\)[ \t,\n]+"
2492                                 "file[ \t,\n]+\\([^ \t\n:)]+\\)")))
2493       (list
2494        (match-string 1)
2495        (match-string 2)))))
2496
2497 ;;; ----------------------------------------------------------------------
2498 ;;;
2499 (defun ti::buffer-parse-line-main ()
2500   "Find directory from the previous 'cd' command.
2501 Look current line first and if it has no directory part,
2502 search backward.
2503
2504 Line formats recognized are:
2505
2506   FILE:LINE: results
2507   FILE LINE results
2508
2509   Or the format can be following, where tokens can span multiple lines
2510
2511   line LINE, file LINE results
2512
2513 Note:
2514
2515   You should probably call `ti::file-name-for-correct-system' to convert
2516   the filename to current Emacs and OS. (Like reading Cygwin paths under
2517   native NT Emacs)
2518
2519 Return:
2520
2521   (file line)         information
2522   nil                 not valid line"
2523   (let* ( ;;       (drive  "\\([a-zA-Z]:\\)?")
2524          (cd-re1 ".*cd +\\(.*\\)")
2525          (cd-re2 "^cd +\\(.*\\)")
2526          path
2527          elt
2528          line
2529          ret
2530          file)
2531     ;; ................................................ grep-format ...
2532     (when (setq elt (or (ti::buffer-parse-grep-line)
2533                         (ti::buffer-parse-grep-line2)))
2534       (setq file (nth 0 elt)
2535             line (nth 1 elt))
2536       ;; ..................................................... Paths ...
2537       (cond                             ;Unix, Dos paths
2538        ((save-excursion
2539           (and (null (string-match (concat "^/\\|^[a-z]:[\\/]") file))
2540                (or (looking-at cd-re1)
2541                    (re-search-backward cd-re2 nil t)))
2542           (setq path (match-string 1))))
2543        (buffer-file-name                ;Another condition
2544         ;; If we loaded erorr log file from the same directory: try it
2545         ;;
2546         ;;   weblint file.html > file.err
2547         ;;
2548         ;;   --> then load file.err into emacs and start jumping to errors.
2549         (setq path (file-name-directory buffer-file-name))))
2550       ;;  ./dir/file --> dir/file
2551       (if (and (stringp file)
2552                (string-match "^\\.[/\\]" file))
2553           (setq file (ti::replace-match 0 nil file)))
2554       (setq ret (list (if path
2555                           (ti::file-make-path path file)
2556                         file)
2557                       line)))
2558
2559     ret))
2560
2561 ;;; ----------------------------------------------------------------------
2562 ;;;
2563 (defun ti::buffer-join-region (beg end)
2564   "Join the region BEG END into a single line."
2565   (interactive "*r")
2566   (save-excursion
2567     (goto-char end)
2568     (while (> (point) beg)
2569       (delete-indentation)))
2570   (beginning-of-line))
2571
2572 ;;; ----------------------------------------------------------------------
2573 ;;;
2574 (defun ti::buffer-read-if-solid ()
2575   "Read from current point all the non-whitespace characters.
2576 Ignores leading and trailing whitespace."
2577   (let* ((eol (line-end-position))
2578          beg
2579          ret)
2580     (save-excursion
2581       (if (looking-at "[ \t]")
2582           (skip-syntax-forward " " eol))
2583       (setq beg (point))
2584       (unless (eolp)
2585         (skip-chars-forward "^ \t" eol)
2586         (if (eq (point) beg)            ;not moved
2587             (end-of-line))              ;no trailing spaces
2588         (unless (eq (point) beg)
2589           (setq ret (buffer-substring beg (point))))))
2590     ret))
2591
2592 ;;; ----------------------------------------------------------------------
2593 ;;;
2594 (defun ti::buffer-read-whitespace (&optional point)
2595   "Gets whitespace following the point or optional at POINT.
2596 Return:
2597   ''     if no whitespace
2598   str    whitespace string"
2599   (let* ((re-w "[ \t]+")                ;whitespace
2600          mp                             ;maximum point, end of line
2601          op)
2602     (save-excursion
2603       (if (null point)
2604           (setq op (point))
2605         (setq op point)
2606         (goto-char point))
2607       (setq mp (line-end-position))
2608       (if (or (null  (looking-at re-w)) ;not sitting on whitespace
2609               (null (re-search-forward re-w mp t)))
2610           ""
2611         (buffer-substring op (point))))))
2612
2613 ;;; ----------------------------------------------------------------------
2614 ;;;
2615 (defun ti::buffer-read-paragraph ()
2616   "Read paragraph at point."
2617   (save-excursion
2618     (beginning-of-line)
2619     (when (looking-at ".*[^ \t]")
2620       (backward-paragraph)
2621       (let* ((beg (point)))
2622         (forward-paragraph)
2623         (buffer-substring beg (point))))))
2624
2625 ;;; ----------------------------------------------------------------------
2626 ;;; - if you use outline or folding, please open the buffer first
2627 ;;;   otw lines cannot be read correcly [the \n is missing if file
2628 ;;;   has closed folds]
2629 ;;;
2630 (defun ti::buffer-read-line (&optional len skip)
2631   "Read whole line from buffer.
2632 Input:
2633
2634   LEN   Only read LEN characters.
2635         If LEN is more than line has characters then return whole line.
2636   SKIP  Ignores SKIP count characters from beginning of line.
2637         If there is not that many to skip, return full line."
2638   (let* ((line     (ti::read-current-line))
2639          (len-full (length line)))
2640     (if (null skip) nil
2641       (cond
2642        ((and len (> len skip))
2643         (setq line (substring line skip)))
2644        ((eq len skip) (setq line ""))))
2645     (if (and len (< len len-full))
2646         (substring line 0 len)
2647       line)))
2648
2649 ;;; ----------------------------------------------------------------------
2650 ;;;
2651 (defun ti::buffer-grep-lines (re &optional beg end inc-prop)
2652   "Greps lines matching RE from buffer.
2653
2654 Optionals:
2655
2656   BEG           default is `point-min'
2657   END           default is `point-max'
2658   INC-PROP      do not remove properties while reading lines.
2659
2660 Return:
2661
2662   nil or  \(str str str ..\)"
2663   (let* ((beg           (or beg (point-min)))   ;point begin
2664          (end           (or end (point-max)))   ;point end
2665          list
2666          line)
2667     (save-excursion
2668       (goto-char beg)
2669       (while (re-search-forward re end t)
2670         (setq line (ti::read-current-line))
2671         (if (null inc-prop)
2672             (setq line (ti::remove-properties line)))
2673         (ti::nconc list line)
2674         (forward-line 1)))
2675     list))
2676
2677 ;;}}}
2678 ;;{{{ buffer: matching, reading words, chars
2679
2680 ;;; ....................................................... &b-reading ...
2681
2682 ;;; ----------------------------------------------------------------------
2683 ;;; The bad thing is that it is impossible slow, so
2684 ;;; use it only when time is not critical (not in loops)
2685 ;;;
2686 (defun ti::buffer-looking-back-at (re)
2687   "Return t if text immediately before point match RE.
2688 This function modifies the match data that `match-beginning',
2689 `match-end' and `match-data' access; save and restore the match
2690 data if you want to preserve them.
2691
2692 Note:
2693   Use only if you need this badly. It's impossible slow."
2694   (let ((beg (point))
2695         ret)
2696     (while (and (null ret)
2697                 (re-search-backward re nil t))
2698       (setq ret (eq (match-end 0) beg)))
2699     (goto-char beg)
2700     ret))
2701
2702 ;;; ----------------------------------------------------------------------
2703 ;;;
2704 (defun ti::buffer-read-char (&optional direction distance)
2705   "Read character towards the DIRECTION from current point.
2706 nil = forward, non-nil backward. DISTANCE 0/nil means reading from
2707 current position.
2708
2709 Return:
2710
2711   nbr   read char value
2712   nil   if the position is not within `point-min-marker' and
2713          `point-max-marker'."
2714   (let* ((beg  (point-min-marker))
2715          (end  (point-max-marker))
2716          (pos  (or distance 0))
2717          (dest (if direction
2718                    (- (point) (1+ pos))
2719                  (+ (point) pos)))
2720          (read (if (or (< dest beg) (> dest end))
2721                    nil
2722                  t)))
2723     (if (null read)
2724         nil                             ;allowed to read ?
2725       (char-after dest))))
2726
2727 ;;; ----------------------------------------------------------------------
2728 ;;; - You can define the "word" syntax here without changing syntax entries.
2729 ;;; - If you want to get word according to current mode's syntax table,
2730 ;;;   use following instead
2731 ;;;
2732 ;;;   (require 'thingatpt)                      ;19.29
2733 ;;;   (word-at-point)
2734 ;;;
2735 (defun ti::buffer-read-word (&optional charset strict)
2736   "Return word specified by optional CHARSET after point.
2737 If optional STRICT is non-nil, requires that point is sitting on
2738 CHARSET before continuing. If there is no CHARSET under point,
2739 search forward for word.
2740
2741 Limitations:
2742
2743   Cannot read word that starts at beginning of buffer
2744
2745 Return:
2746   str         word or nil."
2747   (let* ((charset       (or charset "-a-zA-Z0-9_"))
2748          (not           (concat "^" charset)))
2749     (save-excursion
2750       (if (or (null strict)
2751               (and strict (looking-at charset)))
2752           (buffer-substring
2753            (progn
2754              (skip-chars-forward not)
2755              (skip-chars-backward charset)
2756              (point))
2757            (progn
2758              (skip-chars-forward charset)
2759              (point)))))))
2760
2761 ;;; ----------------------------------------------------------------------
2762 ;;; - This is totally different from the other word reading funcs,
2763 ;;;   it gives you the word separated by spaces. For more finer control see,
2764 ;;;   CHARSET in ti::buffer-read-word
2765 ;;;
2766 (defun ti::buffer-read-space-word ()
2767   "Return word separated by spaces or bol/eol.
2768 If sitting on space or tab, read next word forward. If sitting in the
2769 middle of word, find the word beginning until bol, and start reading from
2770 that point. Point is moved to the beginning of word.
2771
2772 Return:
2773  str
2774  nil    empty line"
2775   (let* ((bol (line-beginning-position))
2776          p)                             ;point
2777     (cond
2778      ((or (bobp)
2779           (equal (char-syntax  (preceding-char)) ?\  ))
2780       ;; At the beginning of word, first char
2781       nil)
2782      ((looking-at "[^ \t\n]")
2783       (setq p (point))
2784       (skip-chars-backward "^ \t\n" bol)
2785       ;;      (skip-syntax-backward " " bol)
2786       (if (eq p (point))                 ;jump not done.
2787           (beginning-of-line)))          ;text starts at bol
2788      ((looking-at "[ \t\n]")
2789       (skip-chars-forward " \t\n"))
2790      ((save-excursion                   ;is the line end of buffer
2791         (end-of-line)                   ;--> e.g. in minibuffer
2792         (eobp))
2793       (beginning-of-line)))
2794     (ti::buffer-read-if-solid)))
2795
2796 ;;; ----------------------------------------------------------------------
2797 ;;;
2798 (defun ti::buffer-read-syntax-word (syntax &optional mode)
2799   "Read block of characters from current point.
2800 Blocks are separated by SYNTAX Normally the block is read
2801 from current point forward.
2802
2803 Input:
2804  SYNTAX         class like \"w\" for words.
2805  MODE    'back  read backward
2806          'word  read full word, skip syntax forward, then backward.
2807
2808 Return:
2809
2810  str
2811  nil    current point does not contain SYNTAX class char."
2812   (let* ((beg  (point))
2813          end
2814          ret)
2815     (save-excursion
2816       (cond
2817        ((eq mode 'back)
2818         (setq end (point))
2819         (skip-syntax-backward syntax)
2820         (setq beg (point)))
2821        ((eq mode 'word)
2822         (skip-syntax-forward syntax) (setq end (point))
2823         (skip-syntax-backward syntax) (setq beg (point)))
2824        (t
2825         (skip-syntax-forward syntax)
2826         (setq end (point)))))
2827     (if (not (eq beg end))
2828         (setq ret (buffer-substring beg end)))
2829     ret))
2830
2831 ;;; ----------------------------------------------------------------------
2832 ;;; #not fully tested
2833 ;;; - Why did I do this after all ?
2834 ;;; - This won't work if cursor it at SPACE and BOL and user wants
2835 ;;;   word BACK
2836 ;;;
2837 ;;;
2838 (defun ti::buffer-read-nth-word (&optional count mode back charset)
2839   "Read COUNT nth word in line.
2840
2841 Input:
2842
2843   COUNT                 defaults to 0 ,current word according to MODE.
2844   MODE   nil            count from the bol/eol.
2845          'end           count from the bol/eol, stop at eol/bol
2846          'this          start counting from this position
2847          'thisEnd       start counting from this position, stop at eol/bol
2848   BACK                  read backward. Affects the mode parameter.
2849   CHARSET               use charset as \"word\", otw defaults to mode's
2850                         word syntax.
2851
2852 Examples:
2853
2854  (ti::buffer-read-nth-word)                  ,return first word in line
2855  (ti::buffer-read-nth-word 5 'end)           ,return 5th word, but stop at eol
2856
2857  ;; return 5th word, counting backwards stopping at bol. Read the word
2858  ;; with charset a-zA-z.
2859
2860  (ti::buffer-read-nth-word 5 'end 'back \"a-zA-Z\")
2861
2862 Caveats:
2863
2864   You get different results, if point is already sitting at word, or
2865   if it's sitting at whitespace, when using 'this modes.
2866   Try yourself with `forward-word' command.
2867
2868   REMEMBER THAT WORD IS MODE DEPENDENT (syntax tables)
2869
2870 Return:
2871
2872   str   word
2873   nil   nth word does not exist."
2874   (let* ((next-func     (if back 'backward-word 'forward-word))
2875          (prev-func     (if back 'forward-word 'backward-word))
2876          (next-skip     (if back 'skip-chars-backward 'skip-chars-forward))
2877          (cmp-func      (if back '< '>))
2878          (count         (or count 0))
2879          limit
2880          ret)
2881     (save-excursion
2882       ;; ... ... ... ... ... ... ... ... ... ... ... ... ...  set limits ...
2883       (if (memq mode '(end nil))        ;starting position
2884           (if back (line-end-position) (line-beginning-position)))
2885       (if (memq mode '(end thisEnd))    ;setting the limit value
2886           (setq limit (if back (line-beginning-position) (line-end-position))))
2887       (if (eq 0 count)
2888           ;; Skip over spaces, stay put ...
2889           (if (ti::char-in-list-case (following-char) '(?\t ?\ ))
2890               (funcall next-skip " \t"))
2891         (funcall next-func count)
2892         (if (ti::char-in-list-case (following-char) '(?\t ?\ ))
2893             (funcall prev-func 1)))
2894       (if (and limit
2895                (funcall cmp-func (point) limit))
2896           nil                           ;limit exceeded
2897         (cond
2898          (charset
2899           (setq ret (ti::buffer-read-word charset)))
2900          (t
2901           (require 'thingatpt)
2902           ;;  silence Bytecomp.
2903           (setq ret (ti::funcall 'word-at-point)))))
2904       ret)))
2905
2906 ;;}}}
2907 ;;{{{ buffer: replacing, modifying lines
2908
2909 ;;; ..................................................... &b-replacing ...
2910
2911 ;;; ----------------------------------------------------------------------
2912 ;;;
2913 (defun ti::buffer-replace-keywords-with-table (keys)
2914   "Function to replace string a with string b.
2915 A and b are stored in a structure and b may be the result of a
2916 computation in itself.  In other words, say we have a list of dotted
2917 pairs like this
2918
2919         ((\"$$AUTHORNAME$$\" . \"Charles R Martin\")
2920          (\"$$TIMESTAMP$$\"   . (current-time-string))
2921
2922 then the function skips through the buffer doing replace-string
2923 $$AUTHORNAME$$ 'Charles R Martin' followed by replace-string
2924 $$TIMESTAMP$$ (results of 'current-time-string')."
2925   (interactive
2926    (list (symbol-value
2927           (intern
2928            (completing-read "Replace keywords using table: "
2929                             obarray
2930                             (lambda (e)
2931                               (and (boundp e)
2932                                    (listp (symbol-value e)))))))))
2933   (mapcar (lambda (x)
2934             (save-excursion
2935               (goto-char (point-min))
2936               (while (search-forward (car x) nil t)
2937                 (replace-match (eval (cdr x))))))
2938           keys))
2939
2940 ;;; ----------------------------------------------------------------------
2941 ;;;
2942 (defsubst ti::buffer-replace-region-with  (beg end string &optional keep-point)
2943   "Replace region BEG END with STRING.
2944 Point is after the inserted string or if KEEP-POINT is non-nil
2945 then point is at BEG."
2946   ;;  Prevent accidental delete
2947   (if (not (stringp string))
2948       (error "Input error."))
2949   ;;  mimic "r" tag region, do not kill that extra char.
2950   (delete-region beg end)
2951   (goto-char beg)
2952   (insert string)
2953   (if keep-point
2954       (goto-char beg)))
2955
2956 ;;; ----------------------------------------------------------------------
2957 ;;; The basic code for this was borrowed from zap-to-char in simple.el
2958 ;;; (define-key esc-map "Z" 'zap-to-regexp) ; originally 'zap-to-char
2959 ;;;
2960 (defun ti::buffer-zap-to-regexp (arg regexp)
2961   "Kill up to and including ARG'th occurrence of REGEXP.
2962 Goes backward if ARG is negative; error if REGEXP not found."
2963   (interactive "p\nsZap to regexp: ")
2964   (kill-region
2965    (point)
2966    (progn
2967      (search-forward-regexp regexp nil nil arg)
2968      ;; This line makes zap-to-regexp behave like
2969      ;; d/ and d? in vi (ie with forward deletion
2970      ;; the regexp is left intact).  Is this
2971      ;; really the right thing?  zap-to-char
2972      ;; dropped this behavior.  Was there a good
2973      ;; reason?  I like this behavior since I use
2974      ;; vi frequently enough to get some benefit
2975      ;; from the orthogonality.
2976      (if (>= arg 0) (search-backward-regexp regexp 1))
2977      ;; p.s.  Yes I know the '=' doesn't really do
2978      ;; much.
2979      (point))))
2980
2981 ;;; ----------------------------------------------------------------------
2982 ;;; #defalias (defalias 'leave-nth-word 'ti::buffer-leave-nth-word)
2983 ;;; - This is great function if you have some column output generated
2984 ;;;   by SQL call or shell call, and you just want THOSE words left...
2985 ;;;
2986 ;;;
2987 (defun ti::buffer-leave-nth-word (beg end &optional nbr strict)
2988   "Delete all between BEG and END except nth word NBR.
2989 Default word nbr is 1, ie. the first word in the line.
2990 The word is considered as space separated entity.
2991
2992 REMEMBER that word is mode dependent !
2993
2994 Input:
2995
2996   NBR           which word top leave on line, range 1..x
2997   STRICT        if non-nil then if word NBR is not found delete whole line"
2998   (interactive "*r\nP")
2999   (let* ((nbr   (or nbr 1))
3000          word)
3001     (save-restriction
3002       (narrow-to-region beg end) (ti::pmin)
3003       (while (not (eobp))
3004         (beginning-of-line)
3005         (setq word (ti::buffer-read-nth-word nbr 'end))
3006 ;;;     (ti::d! word)
3007         (cond
3008          (word
3009           (delete-region (line-beginning-position) (line-end-position))
3010           (insert word)
3011           (forward-line 1))
3012          ((and (null word) strict)
3013           (ti::buffer-kill-line))       ;already does fwd-line
3014          (t
3015           (forward-line 1)))))))
3016
3017 ;;; ----------------------------------------------------------------------
3018 ;;; - Easiest would have been using zap-to-char, but
3019 ;;;   it's not same in 18.xx and 19.xx
3020 ;;; #todo: detect 19.xx and use zap, it's much quicker
3021 ;;;
3022 ;;;
3023 (defun ti::buffer-kill-line (&optional delete count)
3024   "Kill line and move next line up.
3025 If cursor is sitting at the end of buffer, nothing happens.
3026
3027 Input:
3028
3029   DELETE    use `delete-region', which doesn't manipulate `kill-ring',
3030             thus the execution is faster.
3031   COUNT     how many lines to wipe.
3032
3033 Portable:
3034
3035   Between any emacs versions 18.xx - 19.xx
3036
3037 Errors:
3038
3039   Never signalled.
3040
3041 Return:
3042
3043   t             line killed
3044   nil           sitting at eob, cannot kill line"
3045   (interactive "*P")
3046   (let* ((null-line-re "^$")
3047          (count        (or count 1))
3048          (i            0))
3049
3050     ;;  emacs kill-line is little awkward, because if you're at the
3051     ;;  end of buffer it signals an error...
3052
3053     (while (< i count)
3054       (incf i)
3055       (cond
3056        ((eobp)                          ;nothing to kill
3057         nil)
3058        ((and (null (eobp)) (looking-at null-line-re))
3059         (if delete
3060             (delete-char 1)
3061           (kill-line))
3062         t)
3063        (t                               ;shift line up
3064         (beginning-of-line)
3065         (if delete
3066             (delete-region (point) (line-end-position))
3067           (kill-line))
3068         (if (null (eobp))
3069             (if delete
3070                 (delete-char 1)
3071               (kill-line)))
3072         t)))))
3073
3074 ;;; ----------------------------------------------------------------------
3075 ;;;
3076 (defun ti::buffer-strip-control-m () ;;#todo: Emacs function?
3077   "Remove control-M characters from buffer."
3078   (with-buffer-modified
3079     (save-excursion
3080       (ti::pmin)
3081       (while (re-search-forward "\r+$" nil t)
3082         (replace-match "" t t)))))
3083
3084 ;;; ----------------------------------------------------------------------
3085 ;;; #defalias   (defalias 'u2d 'ti::buffer-lf-to-crlf)
3086 ;;;
3087 (defun ti::buffer-lf-to-crlf (&optional arg force)
3088   "Simple Unix to Dos converter. If ARG is non-nil -->  Dos to Unix.
3089 Strips or inserts ^M (return) marker _only_ at the end of line.
3090
3091 If optional FORCE is given, ignores possible write protection.
3092
3093 Example:
3094   (if (ti::file-dos-p)
3095       (ti::buffer-lf-to-crlf 'Dos2unix 'doReadOnly))"
3096   (interactive "P")
3097   (let* ((stat   buffer-read-only))
3098     (cond
3099      ((or (not stat)
3100           (prog1 force (setq buffer-read-only nil))) ;turn it off
3101       ;;  - We use unwind, because the buffer read only status must be
3102       ;;    restored. User may get anxious and press C-g for large buffers...
3103       ;;  - I wonder if we can clear the buffer-modified flag too?
3104       ;;    we leave it untouched for now...
3105       (unwind-protect
3106           (save-excursion
3107             (goto-char (point-min))     ; start at the be.g. of file
3108             (if arg
3109                 ;; ..................................... Dos --> unix ...
3110                 (progn
3111                   (while (search-forward "\015\n" nil t)
3112                     (replace-match "\n"))
3113                   (ti::pmax)
3114                   (beginning-of-line)
3115                   ;; Maybe last line does not have newline?
3116                   (when (looking-at ".*\015$")
3117                     (end-of-line)
3118                     (delete-backward-char 1)))
3119               ;; ....................................... unix --> dos ...
3120               (end-of-line)
3121               (if (not (char= (preceding-char) ?\015))
3122                   (insert "\015"))
3123               (while (not (eobp))
3124                 (forward-line)
3125                 (end-of-line)
3126                 (if (not (char= (preceding-char) ?\015))
3127                     (insert "\015")))))
3128         ;;  restore value
3129         (setq buffer-read-only stat))))))
3130
3131 ;;; ----------------------------------------------------------------------
3132 ;;;
3133 (defun ti::buffer-arrow-control (buffer &optional mode str pos)
3134   "Controls showing the arrow glyph.
3135
3136 Input:
3137   BUFFER        Where to put the arrow, must be visible.
3138   MODE  'show   show the arrow with optional STRING
3139         'hide   remove the arrow. If STR is given, change the value
3140                 of `overlay-arrow-position'. This is usually for restoring
3141                 the original content.
3142         'move   move to current bol position or to POS. STR argument is
3143                 ignored.
3144         any     same as 'hide
3145
3146   STR           arrow string to use, defaults to \"=>\"
3147   POS           any position, converted to beginning of line
3148                 [Emacs docs say the arrow must be at bol]"
3149   (cond
3150    ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ show ^^^
3151    ((or (eq mode 'show)
3152         (eq mode 'move))
3153     ;;  We do not touch the arrow definition, if 'move is the mode
3154     (if (eq mode 'show)
3155         (setq overlay-arrow-string
3156               (if (stringp str) str "=>")))
3157     (or overlay-arrow-position
3158         (setq overlay-arrow-position (make-marker)))
3159     (set-marker overlay-arrow-position
3160                 (if pos
3161                     (progn
3162                       (goto-char pos)
3163                       (line-beginning-position))
3164                   (line-beginning-position))
3165                 buffer))
3166    ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ hide ^^^
3167    (t
3168     (if overlay-arrow-position          ;Kill the marker
3169         (set-marker overlay-arrow-position nil))
3170     (if (stringp str)
3171         (setq overlay-arrow-string str)))))
3172   ;; - Here should be some kind of buffer refresh, since
3173   ;;   the markes isn't hidden, if you're using read-char,
3174   ;;   instead of read-from-minibuffer. See [tinyreply.el] for hack.
3175   ;; - Anybody knows how to refresh the view, please MAIL ME!!
3176 ;;; Not working, I thought moving the cursor would refresh arrow state
3177 ;;;  (save-excursion
3178 ;;;    (select-window (get-buffer-window buffer))
3179 ;;;    (set-buffer buffer)
3180 ;;;    (goto-char (line-beginning-position)))
3181
3182 ;;; ----------------------------------------------------------------------
3183 ;;; #defalias (defalias 'nl 'ti::buffer-insert-line-numbers), see unix nl(1)
3184 ;;;
3185 ;;; -- or is this better ?
3186 ;;; #defalias (defalias 'insert-lines-numbers 'ti::buffer-insert-line-numbers)
3187 ;;;
3188 (defun ti::buffer-insert-line-numbers (beg end &optional line grow format)
3189   "Insert line numbers to buffer.
3190 Mark the region where to insert the line numbers.
3191
3192 The default line format is '%02d:%s' for values lower that 100.
3193 For bigger values the format is dynamical (digit len derived from
3194 start value)
3195
3196 Input:
3197
3198   BEG END       point  area bounds
3199   LINE          nbr    starting line number. 1 is default
3200   GROW          nbr    grow count. 1 is default
3201   FORMAT        str    how line is formatted, see above
3202
3203 Return:
3204
3205   --"
3206   ;;  We input number as string so that user may press return
3207   ;;
3208   (interactive "*r\nsStart line[1]: \nsInterval[1]: ")
3209   (let* (
3210          ;;  convert strings to sensible value
3211          (count         (cond
3212                          ((integerp line) ;; calling lisp
3213                           line)
3214                          (t ;; interactive
3215                           (if (eq 0 (length line))
3216                               1
3217                             (string-to-int line)))))
3218          (factor        (cond
3219                          ((integerp grow)
3220                           grow)
3221                          (t
3222                           (if (eq 0 (length grow))
3223                               1
3224                             (string-to-int grow)))))
3225          (digits        (ti::digit-length count))
3226          ;;  Select "02d" when numbers < 100
3227          ;;  Otw, select "digits" len.
3228          (fmt           (or format
3229                             (concat
3230                              "%0"
3231                              (int-to-string
3232                               (if (or (= digits 1) (eq digits 2))
3233                                   2 digits))
3234                              "d:%s")))
3235          line)
3236     (save-restriction
3237       (narrow-to-region beg end)
3238       (ti::pmin)
3239       (while (not (eobp))
3240         (setq line (ti::read-current-line))
3241         (if (not (string-equal "" line))
3242             (delete-region (point) (line-end-position)))
3243
3244         (insert (format fmt count line))
3245         (setq count (+ count factor))
3246 ;;;     (ti::d! count)
3247         (forward-line 1)))))
3248
3249 ;;; ----------------------------------------------------------------------
3250 ;;; - There must be removing function too.. :-)
3251 ;;; #defalias (defalias 'remove-line-numbers 'ti::buffer-remove-line-numbers)
3252 ;;;
3253 (defsubst ti::buffer-remove-line-numbers (beg end &optional re level)
3254   "Remove line numbers from region BEG END.
3255 The Default line numbers are sticked to the left and have form
3256
3257    xxx: text txt txt
3258
3259 where xxx represent some numbers.
3260
3261 You can supply optional RE and regexp LEVEL that should be
3262 removed. E.g. in normal, above case the
3263
3264   RE    = \"^[0-9]+:\"
3265   LEVEL = 0            ,match whole regexp"
3266   (interactive "*r")
3267   (ti::buffer-replace-regexp
3268    (or re "^[0-9]+:")
3269    (or level 0)
3270    ""
3271    nil
3272    beg
3273    end))
3274
3275 ;;; ----------------------------------------------------------------------
3276 ;;;
3277 (defun ti::buffer-randomize-lines (beg end)
3278   "Scramble all the lines in region BEG END.
3279 If region contains less than 2 lines, lines are left untouched."
3280   (interactive "*r")
3281   (catch 'cancel
3282     (save-restriction
3283       (narrow-to-region beg end)
3284       ;;   Exit when there is not enough lines in region
3285       (if (< (- (point-max) (point-min)) 3)
3286           (throw 'cancel t))
3287       ;;    Prefix lines with a random number and a space
3288       (goto-char (point-min))
3289       (while (not (eobp))
3290         (insert (int-to-string (random 32000)) " ")
3291         (forward-line 1))
3292       ;;  Sort lines according to first field (random number)
3293       (sort-numeric-fields 1 (point-min) (point-max))
3294       (goto-char (point-min))           ;Remove the prefix fields
3295       (while (not (eobp))
3296         (delete-region (point) (progn (forward-word 1) (+ (point) 1)))
3297         (forward-line 1)))))
3298
3299 ;;; ----------------------------------------------------------------------
3300 ;;;
3301 (defun ti::buffer-make-dup-line (&optional count)
3302   "Copy the current line COUNT times (default is 1) below the current line."
3303   (interactive "*p")
3304   (setq count (or count 1))
3305   (save-excursion
3306     (beginning-of-line)
3307     (let ((line (buffer-substring
3308                  (point)
3309                  (progn (forward-line 1) (point)))))
3310       (while (> count 0)
3311         (insert line)
3312         (setq count (1- count))))))
3313
3314 ;;; ----------------------------------------------------------------------
3315 ;;;
3316 (defun ti::buffer-inc-string-nbr (re inc-val increment &optional level)
3317   "Search string and increment integers.
3318
3319 Input:
3320
3321   RE        regexp to match integer. Subexpr 1 assumed in interactive call
3322   INC-VAL   start value.
3323   INCREMENT Step how much to increment every time.
3324   LEVEL     Subexpression in regexp to match the integer portion.
3325
3326 E.g. I you have just paste same variable on the lines multiple times
3327
3328          tablevar10[10]
3329          tablevar10[10]
3330          tablevar10[10]
3331          tablevar10[10]
3332          tablevar10[10]
3333
3334 And now you want to make them unique:
3335
3336          tablevar01[10]
3337          tablevar02[10]
3338          tablevar03[10]
3339          tablevar04[10]
3340          tablevar05[10]
3341
3342 You just give RE \"r\\([0-9]+\\)\" and start value 1, increment 1"
3343   (interactive "sRE: \nnstart value: \nnIncrement: ")
3344   (let* ((level (or level 1))
3345          len
3346          beg
3347          end
3348          fmt)
3349     (while (re-search-forward re nil t) ;search whole buffer
3350       (when (match-end level)
3351         (setq beg (match-beginning level)
3352               end (match-end level)
3353               len (- end beg)
3354               fmt (concat "%0" (int-to-string len) "d"))
3355         (delete-region beg end)
3356         (goto-char beg)
3357         (insert (format fmt inc-val))
3358         (incf inc-val increment)))))
3359
3360 ;;; ----------------------------------------------------------------------
3361 ;;; - Here is slightly different version. this increments every number
3362 ;;;   whereas the previous would increment only SUBMATCH by STEP
3363 ;;;
3364 ;;; - E.g. copying the first line produces:
3365 ;;;
3366 ;;;     assign pi0_vld = (opc_i0 === alu0);
3367 ;;;     assign pi1_vld = (opc_i1 === alu1);
3368 ;;;
3369 (defun ti::buffer-copy-line-and-inc-numbers (&optional increment)
3370   "Copy line, preserving cursor column, and INCREMENT any numbers found.
3371 Prefix ARG is the increment value. Defaults to 1."
3372   (interactive "p")
3373   (let* ((col           (current-column))
3374          (line          (ti::read-current-line))
3375          (increment     (if (integerp increment) increment  1))
3376          len out
3377          mark
3378          num)
3379     (end-of-line)
3380     ;;  We have to use markers, because the line is modified.
3381     (setq mark (point-marker))
3382     (beginning-of-line)
3383     (while (re-search-forward "[0-9]+" (marker-position mark) 1)
3384       (setq len (length (match-string 0)))
3385       (setq num (string-to-int (match-string 0)))
3386       ;;  E.g. 0001 --> 0002
3387       (setq out (format (concat "%0" (int-to-string len) "d")
3388                         (+ increment num)))
3389       (replace-match out))
3390     (beginning-of-line)
3391     (insert line "\n")
3392     (move-to-column col t)
3393     ;; kill marker
3394     (setq mark nil)))
3395
3396 ;;; ----------------------------------------------------------------------
3397 ;;;
3398 (defun ti::buffer-copy-word (n)
3399   "Copy N words above the current line.
3400 If there is no words above the line, then do nothing."
3401   (interactive "p")
3402   (let ((column (current-column))
3403         copy)
3404     (save-excursion
3405       (beginning-of-line)
3406       (if (bobp)
3407           nil
3408         (forward-line -1)
3409         (move-to-column column t)
3410         (setq copy (buffer-substring
3411                     (point)
3412                     (min (save-excursion (end-of-line) (point))
3413                          (save-excursion (forward-word n) (point)))))))
3414     (if copy
3415         (insert copy))))
3416
3417 ;;; ----------------------------------------------------------------------
3418 ;;; #defalias   (defalias 'double-space-region 'ti::buffer-newlines-to-region)
3419 ;;;
3420 (defun ti::buffer-add-newlines-to-region (beg end &optional arg)
3421   "Insert to to the end of each line in region BEG END ARG newlines.
3422 Default is to inser one which makes lines make double spaced."
3423   (interactive "*r\np")
3424   (save-restriction
3425     (narrow-to-region beg end)
3426     (ti::pmin)
3427     (while (search-forward "\n" nil t)
3428       (replace-match
3429        (concat "\n" (make-string arg ?\n))
3430        nil t))))
3431
3432 ;;; ----------------------------------------------------------------------
3433 ;;; - STRICT parameter can be used from lisp call
3434 ;;; #defalias   (defalias 'remove-blank-lines 'ti::buffer-cnv-empty-lines)
3435 ;;;
3436 (defun ti::buffer-cnv-empty-lines (beg end &optional nbr strict)
3437   "Convert empty lines in region BEG END to zero empty lines.
3438 Optionally leaves NBR empty lines. If STRICT is non-nil, all lines
3439 must have NBR amount of empty lines, no more or less.
3440
3441 Point is not preserved."
3442   (interactive "*r\nP")
3443   (let* ((empty-line-re  "^[ \t]+$\\|\n")
3444          (nbr            (or nbr 0)) ;default is to leave no empty lines
3445          pb pe                          ;points beg, end
3446          count
3447          do-it)
3448     (save-restriction
3449       (narrow-to-region beg end)
3450       (ti::pmin)
3451       (while (not (eobp))
3452         (if (null (looking-at empty-line-re))
3453             (forward-line 1)
3454           (setq pb (point))   (skip-chars-forward " \t\n")
3455           (beginning-of-line) (setq pe (point))
3456           ;;  There is a bug in count-lines, that's why we
3457           ;;  use line-end-position,
3458           ;;  not 'pe' to count the lines in region
3459           (setq count (count-lines pb (line-end-position)))
3460           (setq do-it nil)
3461           ;; ...................................................... cond ...
3462           (cond
3463            ((null strict)
3464             (if (> nbr count)
3465                 nil                     ;not that many lines here
3466               (setq do-it t)))
3467            (t
3468             (setq do-it t)))
3469           ;; .................................................... action ...
3470           (cond
3471            ((null do-it)
3472             (forward-line 1))           ;skip
3473            ((> count 0)
3474             (delete-region pb pe)
3475             (setq count nbr)
3476             (while (> count 0)          ;leave that many
3477               (decf count) (insert "\n"))
3478             (if (> count 1)
3479                 (beginning-of-line)
3480               ;;  nothing done, next line
3481               (forward-line)))))))))
3482
3483 ;;; ----------------------------------------------------------------------
3484 ;;; #defalias (defalias 'delete-duplicate-lines 'ti::buffer-del-dup-lines)
3485 ;;;
3486 ;;;  - Letting shell to do the job is the fastest, cleanest
3487 ;;;    way. Sometimes lisp just isn't the right tool...
3488 ;;;
3489 ;;; A. Want to do it fast?
3490 ;;;    Camel book has ready code for this. Pg 228
3491 ;;;    $ perl -ne 'print unless $seen{$_}++' file.in > file.out
3492 ;;;
3493 ;;; B. How about running a shell command over the region/buffer
3494 ;;;    with command "uniq"? This filters successive lines.
3495 ;;;    C-x h , ESC-| uniq RET
3496 ;;;
3497 ;;;
3498 (defun ti::buffer-del-dup-lines(beg end &optional len white-lines)
3499   "Deletes duplicate lines in buffer. Optionally compares first LEN
3500 characters to determine line equality.
3501
3502 Input:
3503
3504   BEG,END       area bounds
3505   LEN           portion of line: chars to compare
3506   WHITE         if non-nil, don't touch whitespace only lines.
3507
3508 Requirements:
3509
3510   Call shell with small PERL program. Make sure PERL is along the path.
3511 "
3512   (interactive "*r\nP")
3513   (let* (cmd)
3514     (save-restriction
3515       (narrow-to-region beg end)
3516       (ti::pmin)
3517       (setq
3518        cmd
3519        (concat
3520         "perl -ne '"
3521         (if len
3522             (concat "$line = substring($_,0, "
3523                     (int-to-string len)
3524                     ");")
3525           "$line = $_;")
3526
3527         (if white-lines
3528             "/^\\s*$/ && do{print; next;};")
3529         "print unless $seen{$line}++;"
3530         "'"))
3531       (shell-command-on-region
3532        (point-min)
3533        (point-max)
3534        ;; replace flag
3535        cmd
3536        t))))
3537
3538 ;;; ----------------------------------------------------------------------
3539 ;;;
3540 (defun ti::buffer-delete-until-non-empty-line (&optional backward point)
3541   "Delete all lines starting from current point.
3542 Stop on [be]obp or non-empty line. Optionally delete BACKWARD
3543 and start at POINT or current position.
3544
3545 Moves point to the beginning of non-empty line."
3546   (interactive "P")
3547   (let* (end)
3548     (when point
3549       (goto-char point))
3550     (beginning-of-line)
3551     (setq point (point))
3552     (cond
3553      (backward
3554       (while (and (not (bobp))
3555                   (looking-at "^[ \t]*$"))
3556         (setq end (point))
3557         (forward-line -1)))
3558      (t
3559       (while (and (not (eobp))
3560                   (looking-at "^[ \t]*$"))
3561         (forward-line 1)
3562         (setq end (point)))))
3563     (if end
3564         (delete-region point end))))
3565
3566 ;;; ----------------------------------------------------------------------
3567 ;;; - The delete-region, according to emacs C-developers,
3568 ;;;   is _lighting_ fast way to do deletions in emacs.
3569 ;;;
3570 (defun ti::buffer-trim-blanks (beg end)
3571   "Delete trailing blanks in region BEG END."
3572   (interactive "*r")
3573   (save-restriction
3574     (save-excursion
3575       (narrow-to-region beg end)
3576       ;;  _much slower would be:       (replace-regexp "[ \t]+$" "")
3577       (goto-char (point-min))
3578       (while (not (eobp))
3579         (end-of-line)
3580         (delete-horizontal-space)
3581         (forward-line 1))))
3582   nil)                                  ;for possible hook
3583
3584 ;;; ----------------------------------------------------------------------
3585 ;;;
3586 (defun ti::buffer-replace-regexp (re level str &optional back beg end)
3587   "Like `replace-regexp' but for Lisp programs.
3588 Lisp info page says in \"Node: Style Tip\", that lisp programs shouldn't
3589 use `replace-regexp', so here is identical function that doesn't touch
3590 the mark. The point is left after last match.
3591
3592 Input:
3593
3594   RE        regexp
3595   LEVEL     subexpression
3596   STR       string used in replacing.
3597   BACK      replace backward
3598   BEG END   region. If both BEG and END is given, the
3599             BACK parameter is ignored."
3600   (let* ((func (if back 're-search-backward 're-search-forward))
3601          bp
3602          ep)
3603     (if (not (integerp level))          ;common error
3604         (error "Level is not integer."))
3605     (cond
3606      ((and beg end)
3607       (setq bp beg  ep end  func 're-search-forward))
3608      ((and back end)
3609       (setq bp (point)  ep (point-min)))
3610      ((and back beg)
3611       (setq bp beg  ep (point-min)))
3612      ((and (not back) beg)
3613       (setq bp beg ep (point-max)))
3614      ((and (not back) end)
3615       (setq bp (point) ep end))
3616      (t                                 ;fall thru case
3617       (setq bp (point)  ep (point-max))))
3618     (save-restriction
3619       (narrow-to-region bp ep)
3620       (ti::pmin)
3621       (while (and (funcall func re nil t)
3622                   (not (eobp)))
3623         (if (null (match-end level)) nil ;not matched
3624           (ti::replace-match level)
3625           ;; point is at the end of STR inserted
3626           (insert str))))))
3627
3628 ;;}}}
3629 ;;{{{ buffer: misc
3630
3631 ;;; ..................................................... &buffer-misc ...
3632
3633 ;;; ----------------------------------------------------------------------
3634 ;;;
3635 (defun ti::buffer-diff-type-p ()
3636   "Check the diff type in buffer.
3637 Assumes that whole buffer contains diff. Searches for traces.
3638 Lines must be left flushed.
3639
3640    *** /tmp/T.11  Fri Oct 20 12:22:51 1995
3641    --- /tmp/T.1   Fri Oct 20 12:24:29 1995
3642    ***************
3643
3644 Normal diff shows:
3645
3646    20,21d19
3647    < clrFamily;
3648    < clrInfo;
3649
3650 Gnu diff -n (or --rcs, Output an RCS format diff)
3651
3652     d696 1
3653     a696 1
3654             (tdi-goto-kbd 'verb)
3655     d704 2
3656     a705 2
3657
3658 Gnu diff -u (unified diff)
3659
3660     @@ -17,6 +17,8 @@
3661      bAnsTime[16+1];
3662      clearCode;
3663      endChargeTime[16+1];
3664     +clrFamily;
3665     +clrInfo;
3666      statClrTime[16+1];
3667      clearPart;
3668      aDirNbrType;
3669
3670 Returns:
3671   cons cell
3672     (TYPE . POS)        ,POS is the diff start position
3673     nil                 ,no diff found
3674
3675   TYPE can be
3676     'context            ,context diff -c
3677     'gnu-n              ,gnu diff -n
3678     'gnu-u              ,gnu diff -u
3679     'normal             ,normal diff
3680
3681  POS
3682     character position where the first diff was found"
3683   (let* ((re-c1     "^[ \t]*[*][*][*] [0-9]") ;context diff regexps
3684
3685          ;;   The normal diff line is following, but PGP breaks it.
3686          ;;   That's why we have those ? ? in thge regexp
3687          ;;   --- 1.2.1.1
3688          ;;   - --- 1.2.1.1
3689          ;;
3690          (re-c2     "^-? ?--- .")
3691          (re-c3     (concat  "^" (regexp-quote "***************")))
3692          (re-n1     "^[0-9]+[dca][0-9]+$\\|^[0-9]+,[0-9]+[dca][0-9]")
3693          (re-n2     "^[<>]")
3694          ;;  Gnu types
3695          (re-gn1            "^[dac][0-9]+ [0-9]+$")
3696          (re-gu1            "^@@ [-+][0-9]+,[0-9]+[ \t]+[-+]+")
3697          type
3698          pos
3699          ret)
3700     (save-excursion
3701       (ti::pmin)
3702       (cond
3703        ;; ... ... ... ... ... ... ... ... ... ... ... ... ...  context ..
3704        ((and (re-search-forward re-c1 nil t)
3705              (setq pos (line-beginning-position))
3706              (or (save-excursion
3707                    (and (progn
3708                           (forward-line 1)
3709                           (looking-at re-c2))
3710                         (progn
3711                           (forward-line 1)
3712                           (looking-at re-c3))))
3713                  (save-excursion
3714                    (forward-line -1)
3715                    (looking-at re-c3))))
3716         (setq type 'context))
3717        ;; ... ... ... ... ... ... ... ... ... ... ... ... ... . normal ..
3718        ((and (re-search-forward re-n1 nil t)
3719              (setq pos (line-beginning-position))
3720              (progn
3721                (forward-line 1)
3722                (looking-at re-n2)))
3723         (setq type 'normal))
3724        ((re-search-forward re-gu1 nil t)
3725         ;;  There is filename information above the diff start.
3726         ;;  --- file.xx
3727         ;;  +++ file.xx
3728         ;;
3729         (forward-line -2)
3730         (setq pos (point))
3731         (setq type 'gnu-u))
3732        ((and (re-search-forward re-gn1 nil t) ;require two same lines
3733              (setq pos (line-beginning-position))
3734              (progn
3735                (forward-line 1)
3736                (looking-at re-gn1)))
3737         (setq type 'gnu-n)))
3738       (if (and type pos)
3739           (setq ret (cons type pos)))
3740       ret)))
3741
3742 ;;; ----------------------------------------------------------------------
3743 ;;;
3744 (defun ti::buffer-outline-widen ()
3745   "Open folded/outlined buffer if some of the modes is active.
3746 You have to call this function if you want to do something for
3747 the whole buffer."
3748   (interactive)
3749
3750   ;;  Unfold the buffer, so that we can see all.
3751   ;;  We must also preserve point
3752
3753   (ti::save-with-marker-macro
3754     (and (boundp 'folding-mode)
3755          ;;  No autoloads allowed, this makes sure the fboundp
3756          ;;  is converted to real function. The ti::funcall command
3757          ;;  cannot use autoload function.
3758          (progn (require 'folding) t)
3759          (if (symbol-value 'folding-mode) ;ByteComp silencer
3760              (ti::save-line-column-macro nil nil
3761                ;; ByteComp silencer
3762                (ti::funcall 'folding-open-buffer))))
3763     (and (eq major-mode 'outline-mode)
3764          (fboundp 'show-all)
3765          (progn (require 'outline) t)
3766          (ti::save-line-column-macro nil nil
3767            (ti::funcall 'show-all)))
3768     (and (boundp 'outline-minor-mode)
3769          (fboundp 'show-all)
3770          (progn (require 'outline) t)
3771          (ti::save-line-column-macro nil nil
3772            (ti::funcall 'show-all)))))
3773
3774 ;;; ----------------------------------------------------------------------
3775 ;;;
3776 (defun ti::buffer-buffer-list-files (&optional re str)
3777   "Return all files loaded into Emacs.
3778
3779 If optional RE and STR are given, then a file name substitution
3780 takes place:
3781
3782  args           RE = \"/usr43/users/john/\"   STR = \"~/\"
3783  buffer file    \"/usr43/users/john/t.txt\"
3784  substituted    \"~/t.txt\"
3785
3786 Example:
3787
3788  (ti::buffer-buffer-list-files \"/usr43/users/john\" \"~\")
3789
3790 Return:
3791
3792  (filename ..)      list of filenames"
3793   (let* (list
3794          file)
3795     (dolist (elt (buffer-list))
3796       (setq file  (buffer-file-name elt))
3797       (when (stringp file)         ;might be nil if buffer has no file
3798         (if (and re str
3799                  (string-match re file))
3800             (setq file (ti::replace-match 0 str file)))
3801         (push file  list)))
3802     list))
3803
3804 ;;; ----------------------------------------------------------------------
3805 ;;;
3806 (defun ti::buffer-count-words (beg end)
3807   "Count words in region BEG END."
3808   (interactive "r")
3809   (let ((msg (count-matches "\\w*" beg end)))
3810     (when (and msg
3811                (string-match "\\([0-9]+\\)" msg))
3812       (string-to-int msg))))
3813
3814 ;;; ----------------------------------------------------------------------
3815 ;;; - This is quite a handy function when you're programming e.g.
3816 ;;;   in C++ and want to know how many chars are in the string.
3817 ;;;
3818 (defun ti::buffer-count-chars-in-delimited-area (arg &optional verb)
3819   "Counts characters within quotes. ARG C - u to search single quotes.
3820 Other argument invokes asking the beginning delimiter: if you give
3821 \"(\"  the end delimiter is automatically set to \")\".
3822 This function is mainly for interactive use. VERB.
3823
3824 Return:
3825   nbr   count of characters
3826   nil   begin or end delimiter was not found"
3827   (interactive "P")
3828   (let* ((alist '(( ?\(  ?\) )
3829                   ( ?\{  ?\} )
3830                   ( ?\[  ?\] )
3831                   ( ?\`  ?\' )
3832                   ( ?\<  ?\> )))
3833          (verb   (or verb (interactive-p)))
3834          beg-ch
3835          end-ch
3836          beg-re
3837          end-re
3838          re
3839          elt
3840          point
3841          ret)
3842     ;; ... ... ... ... ... ... ... ... ... ... ... ... . preliminaries ...
3843     (setq
3844      re   (cond
3845            ((equal arg nil)
3846             "\"")
3847            ((equal arg '(4))
3848             "'")
3849            (t
3850             (message "Begin delimiter char: ")
3851             (setq beg-ch (read-char))
3852             (setq end-ch
3853                   (if (setq elt (assq beg-ch alist))
3854                       (nth 1 elt)
3855                     ;;   Can't find match for it, so use same char
3856                     ;;   for both delimiters
3857                     beg-ch))
3858             nil)))
3859     (if re                              ;now, what we got?
3860         (setq beg-re (regexp-quote re)   end-re beg-re)
3861       (setq beg-re (regexp-quote (char-to-string beg-ch))
3862             end-re (regexp-quote (char-to-string end-ch))))
3863     ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... . do it ...
3864     (save-excursion
3865       (if (null (re-search-forward end-re nil t))
3866           (and verb
3867                (message (concat "Can't find end mark: " end-re)))
3868         (setq point (point))
3869         ;;  the re-search-forward leaves point after the char,
3870         ;;  we have to go small step back before we change the direction.
3871         (forward-char -1)
3872         (if (null (re-search-backward beg-re nil t))
3873             (and verb
3874                  (message (concat "Can't find beginning mark: " beg-re)))
3875           ;; the -2 excludes the markers itself.
3876           ;;
3877           (setq ret (- (length
3878                         (buffer-substring point (point)))
3879                        2))
3880           (and verb
3881                (message (concat (int-to-string ret) " characters."))))))
3882     ret))
3883
3884 ;;; ----------------------------------------------------------------------
3885 ;;;
3886 (defun ti::buffer-word-move (set &optional back)
3887   "Move to next word defined in SET, optionally BACK.
3888 SET must be string, that can be turned into regexp and that can
3889 be used with skip-chars functions.
3890
3891 E.g. \"-[]$%@#&*\":;{}()<>/\\ \t\n\""
3892   (interactive)
3893   (let* ((nset          (concat "^" set)) ;not-set
3894          (set-re        (concat "[" (regexp-quote set) "]"))
3895          (char          (char-to-string
3896                          (if back
3897                              (preceding-char)
3898                            (following-char))))
3899          (point (point)))
3900     (cond
3901      (back
3902       (if (string-match set-re char)
3903           (progn
3904             (skip-chars-backward set)
3905             (skip-chars-backward nset))
3906         ;;  If we're over word already, this moves. But if we're
3907         ;;  at the beginning of word this doesn't move.
3908         ;;
3909         (skip-chars-backward nset)
3910         (when (eq (point) point)
3911           (skip-chars-backward set)
3912           (skip-chars-backward nset))))
3913      (t
3914       (if (string-match set-re char)
3915           (progn
3916             (skip-chars-forward set)
3917             (skip-chars-forward nset))
3918         (skip-chars-forward nset)
3919         (skip-chars-forward set))))))
3920
3921 ;;; ----------------------------------------------------------------------
3922 ;;;
3923 (defun ti::buffer-find-duplicate-same-word (&optional back)
3924   "Find consecutive occurrences of same word, optionally search BACK."
3925   (interactive "P")
3926   (let* ((func  (if back 're-search-back 're-search-forward)))
3927     (if (funcall func "\\(\\<\\w*\\>\\)[ \t\n]*\\1" nil t)
3928         (isearch-highlight (match-beginning 0) (match-end 0))
3929       nil)))
3930
3931 ;;; ----------------------------------------------------------------------
3932 ;;;
3933 (defun ti::buffer-move-paragraph-to-column (beg end col)
3934   "Move text BEG END to column position COL.
3935
3936 The indent is done in the following way:
3937 o  untabify region
3938 o  Search first non-whitespace line starting from the beginning
3939    of region.
3940 o  count how much the line is indented: remove that indentation
3941    from all the rest of the lines.
3942 o  Now when lines have no indet; reindent to COL
3943
3944 The procedure described preserves the actual paragraph style, so that
3945 if text inside paragraph is more indented that the previous line the
3946 relative indent is preserved.
3947
3948     txt txt txt txt txt txt
3949     txt txt txt txt txt txt
3950       inner indent txt txt txt
3951       inner indent txt txt txt
3952     txt txt txt txt txt txt
3953     txt txt txt txt txt txt
3954
3955 Input:
3956
3957   beg   always calculates to bol
3958   end   always calculates to eol"
3959   (interactive "*r\np")
3960   (let (min
3961         marker
3962         len)
3963     (goto-char (min beg end))           ;Setting MIN
3964     (setq min (line-beginning-position))
3965     (goto-char (max beg end))           ;setting MAX
3966     (end-of-line)
3967     (setq marker (point-marker))        ;Because untabify moves end
3968     (untabify min (marker-position marker))
3969     ;;  Is there non whitespace line?
3970     (goto-char min)
3971     (cond
3972      ((re-search-forward "^[^ \n]" (marker-position marker)  t)
3973       ;;  non whitespace line found.
3974       ;;  Do nothing -- indent directly
3975       nil)
3976      ((re-search-forward "^\\( +\\)[^ \n]" (marker-position marker)  t)
3977       ;;  Remove this indentation.
3978       (when (> (setq len (length (or (match-string 1) ""))) 0)
3979         (indent-rigidly min (marker-position marker) (- 0 len)  ))))
3980     ;;  Now reindent the region
3981     (indent-rigidly min (marker-position marker) col) ;new
3982     ;; Kill marker
3983     (setq marker nil)))
3984
3985 ;;; ----------------------------------------------------------------------
3986 ;;;
3987 (defsubst ti::buffer-move-to-col (col)
3988   "Doesn't care about line length. Insert spaces to get to COL.
3989 Convert tabs to spaces to get to exact COL."
3990   (interactive "Nto col: ")
3991   (move-to-column col t)
3992   (if (not (eq (current-column) col))
3993       (while (< (current-column) col)
3994         (insert " "))))
3995
3996  ;;;;; Experimental
3997  ;;;(defun space-to-column (target)
3998  ;;;  "Insert spaces as necessary to move pt to TARGET column."
3999  ;;;  (interactive "p")
4000  ;;;  (let ((cur (current-column)))
4001  ;;;    (if (< cur target)
4002  ;;;        (insert (make-string (- target cur) ? )))))
4003
4004 ;;}}}
4005 ;;{{{ buffer: selective display
4006
4007 ;;; ................................................... &misc-packages ...
4008
4009 ;;; ----------------------------------------------------------------------
4010 ;;; - Separating the "effective display" is easy with this...
4011 ;;;
4012 ;;;
4013 (defun ti::buffer-selective-display-copy-to  (beg end buffer &optional verb)
4014   "Copy region BEG END selective display to BUFFER. VERB.
4015 E.g. folding.el and outline based modes use selective display."
4016   (interactive
4017    (progn
4018      (if (not (region-active-p))
4019          (error "Region not selected."))
4020      (list
4021       (region-beginning)
4022       (region-end)
4023       (read-from-minibuffer "To buffer: " "*selective display*"))))
4024   (let* ((bp    (get-buffer-create buffer))  ;barfs if invalid...
4025          (bp    (ti::temp-buffer bp 'clear)) ;ok, use it
4026          line)
4027     (ti::verb)
4028     (save-excursion
4029       (save-restriction
4030         (narrow-to-region beg end)
4031         (ti::pmin)
4032         (while (not (eobp))
4033           ;;  - Reset for normal lines.
4034           ;;  - Or reads until \r. I.e. the hidden part is not read
4035           (setq line (or (and (looking-at ".*\r")
4036                               (concat
4037                                (ti::buffer-match  "\\([^\r]+\\)+\r" 1)
4038                                "..."))
4039                          (ti::read-current-line)))
4040
4041           (setq line (concat line "\n"))
4042           (forward-line 1)
4043           (ti::append-to-buffer bp line))))
4044     (if verb
4045         (pop-to-buffer bp))))
4046
4047 ;;; ----------------------------------------------------------------------
4048 ;;; - Print folding.el and outline based buffer with this...
4049 ;;;
4050 (defun ti::buffer-selective-display-print  (beg end)
4051   "Print selective display region BEG END."
4052   (interactive "r")
4053   (let* ((buffer  (generate-new-buffer "*print*")))
4054     (unwind-protect
4055         (progn
4056           (ti::buffer-selective-display-copy-to beg end buffer)
4057           (with-current-buffer buffer (print-buffer)))
4058       (kill-buffer buffer))))
4059
4060 ;;}}}
4061 ;;{{{ Window, frames
4062
4063 ;;; .......................................................... &window ...
4064
4065 ;;; ----------------------------------------------------------------------
4066 ;;;
4067 (defun ti::window-frame-list  (&optional all exclude-current win)
4068   "Return only frames that are non-dedicated.
4069 Input:
4070   ALL                   if non-nil, return all frames.
4071   EXCLUDE-CURRENT       if non-nil, exclude current active frame.
4072   WIN                   Use this is as a current window when searching
4073                         current frame."
4074   (let* ((oframe  (if win
4075                       (window-frame win)
4076                     (selected-frame)))
4077          flist
4078          ret)
4079     (if exclude-current
4080         (setq flist (delete oframe (frame-list)))
4081       (setq flist (frame-list)))
4082     (dolist (frame flist)
4083       (select-frame frame)
4084       (if (or all (not (window-dedicated-p (selected-window))))
4085           (ti::nconc ret frame)))
4086     (if (framep oframe)
4087         (select-frame oframe))          ;Return back to original
4088     ret))
4089
4090 ;;; ----------------------------------------------------------------------
4091 ;;;
4092 (defun ti::window-list (&optional buffers)
4093   "Gather all visible windows or BUFFERS visible in current frame."
4094   (let* ((s     (selected-window))      ;start window
4095          (loop  t)
4096          (w     s)                      ;current cycle
4097          l
4098          ww)
4099
4100     (if buffers                         ;Start list
4101         (setq l (list (window-buffer s)))
4102       (setq l (list s)))
4103
4104     (while loop
4105       (setq ww (next-window w))
4106       (setq w ww)                       ;change
4107       (other-window 1)                  ;move fwd
4108       (if (eq ww s)                     ;back to beginning ?
4109           (setq loop nil)
4110
4111         (if buffers                     ;list of buffers instead
4112             (setq ww (window-buffer ww)))
4113         (setq l (cons ww l))))
4114     (nreverse l)))
4115
4116 ;;; ----------------------------------------------------------------------
4117 ;;;
4118 (defsubst ti::window-single-p ()
4119   "Check if there is only one window in current frame."
4120   ;;  No need to run `length' when `nth' suffices.
4121   (let* ((win      (selected-window))
4122          (next     (next-window)))
4123     ;;  Same window?
4124     (eq win next)))
4125
4126 ;;; ----------------------------------------------------------------------
4127 ;;;
4128 (defun ti::window-get-buffer-window-other-frame  (buffer)
4129   "Return (frame . win). If BUFFER is visible..
4130 in some other frame window than in the current frame."
4131   (let* (win
4132          ret)
4133     (dolist (frame
4134              (delete (selected-frame) (frame-list)))
4135       ;;  maybe in other frame...
4136       (when (setq win (get-buffer-window buffer frame))
4137         (setq ret (cons frame win))
4138         (return)))
4139     ret))
4140
4141 ;;; ----------------------------------------------------------------------
4142 ;;; - don't know good way how to generalize this to return either top/bottom
4143 ;;;   window. I guess we just copy this and make small changes...
4144 ;;; - Does anyone have good suggestions to do therwise?
4145 ;;;
4146 (defun ti::window-find-bottom  (win-list)
4147   "Find bottom window from WIN-LIST.
4148 Any non-visible window in list is skipped.
4149 If there are adjacent windows, return all of them.
4150
4151         -------------
4152         |           |   <- top window
4153         -------------
4154         |  |   |    |   < three splitted windows at the bottom
4155         | A| B |  C |
4156         -------------
4157
4158 Return:
4159   list          single or many windows. In any order."
4160   (let* (data
4161          top
4162          top-cmp
4163          bot
4164          bot-cmp
4165          win-val
4166          init)
4167     (dolist (win win-list)
4168       (setq data (window-edges win))
4169       (if (null init)                   ;init vars
4170           (setq init    t               ;initalized ok
4171                 win-val (list win)      ;win comes from 'window-loop'
4172                 top     (nth 1 data)
4173                 bot     (nth 3 data)))
4174
4175       (setq top-cmp     (nth 1 data)
4176             bot-cmp     (nth 3 data))
4177       (cond
4178        ((> bot-cmp bot)                 ;this is more lower
4179         (setq win-val (list win)
4180               top     top-cmp
4181               bot     bot-cmp))
4182        ((or (eq bot-cmp bot)           ;hmm, same horizontal top row..
4183             (eq top-cmp top))           ;split sideways...
4184         (push win win-val))
4185        ((or (eq bot-cmp bot)            ;  .........
4186             (> top-cmp  top))           ;  ....    .
4187                                         ;  .........  < pick lowest in left
4188         (setq win-val (list win)
4189               top     top-cmp
4190               bot     bot-cmp))))
4191     win-val))
4192
4193 ;;; ----------------------------------------------------------------------
4194 ;;;
4195 (defun ti::window-match-buffers (buffer-name-list)
4196   "Check all windows that match BUFFER-LIST.
4197
4198 Input:
4199
4200   BUFFER-NAME-LIST      ,strings, list of buffer names.
4201
4202 Return:
4203
4204   '((BUFFER-NAME WIN-PTR WIN-PTR ..)
4205     (BUFFER-NAME ..)
4206     ..)"
4207   (let* (alist
4208          buffer
4209          ptr
4210          p)
4211     (dolist (win (ti::window-list))
4212       ;;  last walue will tell the BOTTOM
4213       (setq buffer      (buffer-name (window-buffer win)))
4214       ;;  Create alist
4215       ;;  '((BUFFER-NAME WIN-PTR WIN-PTR ..)
4216       ;;    (BUFFER-NAME ..))
4217       (cond
4218        ((member buffer buffer-name-list) ;does it interest us ?
4219         (cond
4220          ((not (setq ptr (assoc buffer alist))) ;; create initial element
4221           (push (list buffer win) alist))
4222          (t                             ;; add element
4223           (setq p  (cdr ptr))           ;drop 1st element away
4224           (ti::nconc p win)             ;add new element
4225           ;;  replace with new list
4226           (setcdr ptr p))))))
4227     (nreverse alist)))
4228
4229 ;;}}}
4230 ;;{{{ Key maps, translations
4231
4232 ;;; ----------------------------------------------------------------------
4233 ;;;
4234 (defun ti::keymap-single-key-definition-p (key-def)
4235   "Check if KEY-DEF is a single key definition.
4236 E.g. If you want to check if prefix key is composed only from
4237 one key: \"a\" \"?\\C-a\"  or even [(?a)].
4238
4239   (ti::keymap-single-key-definition-p [ a ] )  --> a
4240   (ti::keymap-single-key-definition-p [(a)] )  --> a
4241   (ti::keymap-single-key-definition-p \"a\" )    --> a
4242   (ti::keymap-single-key-definition-p \"\\C-a\" ) --> C-a
4243
4244   (ti::keymap-single-key-definition-p [(a) (b)] )  --> nil
4245   (ti::keymap-single-key-definition-p [(meta a)])  --> nil
4246   (ti::keymap-single-key-definition-p \"ab\" )       --> nil
4247   (ti::keymap-single-key-definition-p \"?C-ab\" )    --> nil
4248
4249 Return:
4250
4251   If single key. Return it, either as character or symbol."
4252   (let* ((key (cond
4253                ((and (stringp key-def) ;; "\C-a" or "a"
4254                      (eq 1 (length key-def)))
4255                 (string-to-char key-def))
4256                ((and (vectorp key-def) ;; [(ELT)] or [ELT]
4257                      (eq 1 (length key-def))
4258                      (eq 1 (length (elt key-def 0))))
4259                 (let* ((ELT  (elt key-def 0))
4260                        (item (if (listp ELT) ;; was [(ELT)]
4261                                  (car ELT)
4262                                ELT)) ;; otherwise [ELT]
4263                        ;;  At this point; convert to string
4264                        (ch   (cond
4265                               ((symbolp item) ;; mouse-1 ot the like
4266                                item)
4267                               ((characterp item) ;; was it ?a ==> [(?a)]
4268                                item)
4269                               ((and (stringp item)
4270                                     (eq 1 (length item)))
4271                                (string-to-char item)))))
4272                   ch)))))
4273     key))
4274
4275 ;;; ----------------------------------------------------------------------
4276 ;;;
4277 (defun ti::keymap-define-key-backspace ()
4278   "Move C-h to Backspace if this is non-windowed Emacs.
4279 Key C-x C-? replaces original C-x C-h.
4280 Key C-c h   replaces original C-h call
4281 "
4282   (interactive)
4283   (let* (;;;     (DELETE    "\C-h")
4284          (BACKSPACE "\177"))
4285     (unless (ti::compat-window-system)
4286       (defvar key-translation-map (make-sparse-keymap))
4287       ;;  If it's nil then something is wrong. Fix it.
4288       (unless key-translation-map
4289         (setq key-translation-map (make-sparse-keymap)))
4290       ;; This keymap works like `function-key-map', but comes after that,
4291       ;; and applies even for keys that have ordinary bindings.
4292       (define-key key-translation-map "\177" "\C-h")
4293       (define-key key-translation-map "\C-h" "\177")
4294       (global-set-key BACKSPACE 'backward-delete-char)
4295       (flet ((key-warning
4296               (key def)
4297               (message "tinylib: Warning, key already occupied: %s %s"
4298                        key def)))
4299         ;; (ti::define-key-if-free global-map
4300         ;;   "\C-x\C-?" 'help-for-help 'key-warning)
4301         (ti::define-key-if-free global-map
4302                                 "\C-ch"    'help-command  'key-warning)))))
4303
4304 ;;; ----------------------------------------------------------------------
4305 ;;;
4306 (defun ti::keymap-function-bind-info  (function-sym &optional map)
4307   "Return binding information for FUNCTION-SYM from MAP as string or nil."
4308   (let* ((gm  (current-global-map))
4309          global-bindings
4310          local-bindings
4311          bind-info)
4312     (setq global-bindings (where-is-internal function-sym)
4313           local-bindings
4314           (prog2
4315               ;;  We have to set this to nil because where-is-internal
4316               ;;  searches global map too. We don't want that to happen
4317               ;;
4318               (use-global-map (make-keymap))
4319               (where-is-internal
4320                function-sym
4321                (or map (current-local-map)))
4322             (use-global-map gm)))
4323     (setq
4324      bind-info
4325      (if (or global-bindings local-bindings)
4326          (format "%s%s%s"
4327                  (if global-bindings
4328                      (format "global %s"
4329                              (mapcar 'key-description
4330                                      global-bindings))
4331                    "")
4332                  (if (and global-bindings local-bindings)
4333                      " and "
4334                    "")
4335                  (if local-bindings
4336                      (format "local to %s"
4337                              (mapcar 'key-description
4338                                      local-bindings))
4339                    ""))))
4340     bind-info))
4341
4342 ;;; ----------------------------------------------------------------------
4343 ;;;
4344 ;;;  because of the nature of minor modes, changes in the maps
4345 ;;;  are not reflected unless, the minor mode is installed again
4346 ;;;
4347 ;;;  The following removes minor keymap, if it exists,
4348 ;;;  and reinstalls it with new added bindings.
4349 ;;;
4350 (defun ti::keymap-reinstall-minor-mode (mode-name-symbol)
4351   "Reinstall minor mode MODE-NAME-SYMBOL.
4352 This is needed if you have made changes to minor modes keymaps.
4353 They don't take in effect until you reinstall the minor mode.
4354
4355 Return:
4356  t       minor mode found and reinstalled
4357  nil     no susch minor mode."
4358   (let* (sym
4359          mode-string
4360          elt
4361          map-sym
4362          map)
4363     (when (setq elt (assq mode-name-symbol minor-mode-alist))
4364       (setq mode-string (nth 1 elt))
4365       (setq elt (assq mode-name-symbol minor-mode-map-alist))
4366
4367       (unless elt
4368         (error "No map for minor mode %s"  mode-name-symbol))
4369       (setq sym (concat
4370                  (symbol-name mode-name-symbol)
4371                  "-map"))
4372       (setq map-sym (intern-soft sym))
4373       (if (or (null map-sym)
4374               (not (keymapp (setq map (eval map-sym)))))
4375           (error "The keymap was not found %s" map-sym))
4376       (ti::keymap-add-minor-mode mode-name-symbol nil nil 'remove)
4377       (ti::keymap-add-minor-mode mode-name-symbol mode-string map))))
4378
4379 ;;; ----------------------------------------------------------------------
4380 ;;; - Why doesn't emacs offer this simple interface by default ?
4381 ;;;
4382 (defun ti::keymap-add-minor-mode
4383   (mode-func-sym mode-name-sym  mode-map &optional remove)
4384   "Add the minor mode into Emacs. If mode exists, do nothing.
4385
4386 Input:
4387
4388   MODE-FUNC-SYM         function symbol, mode to turn on
4389   MODE-NAME-SYM         variable  symbol to hold mode name string
4390   MODE-MAP              keymap
4391   REMOVE                OPTIONALLY removes mode with mode-name-sym
4392
4393 Examples:
4394
4395    ;;  to add mode
4396    (ti::keymap-add-minor-mode 'foo-mode 'foo-mode-name  foo-mode-map)
4397
4398    ;;  to remove mode
4399    (ti::keymap-add-minor-mode 'foo-mode nil nil 'remove)"
4400
4401   (let* (elt)
4402     (cond
4403      ((null remove)
4404       (or (assq mode-func-sym minor-mode-map-alist)
4405           (setq minor-mode-map-alist
4406                 (cons (cons mode-func-sym  mode-map)
4407                       minor-mode-map-alist)))
4408       ;;  Update minor-mode-alist
4409       (or (assq  mode-func-sym minor-mode-alist)
4410           (setq minor-mode-alist
4411                 (cons (list mode-func-sym mode-name-sym)
4412                       minor-mode-alist))))
4413      (t
4414       (and (setq elt (assq mode-func-sym minor-mode-map-alist))
4415            (setq minor-mode-map-alist (delq elt minor-mode-map-alist)))
4416
4417       (and (setq elt (assq mode-func-sym minor-mode-alist))
4418            (setq minor-mode-alist (delq elt minor-mode-alist)))))))
4419
4420 ;;; ----------------------------------------------------------------------
4421 ;;;
4422 (defun ti::keymap-bind-control (map-symbol get-set prop key)
4423   "Get or set the stored property binding in map.
4424 This is a good function to use if you modify the original
4425 bindings in the map. You can then call the original
4426 function behind the binding in your modified function.
4427
4428 Input:
4429
4430   MAP-SYMBOL    map name
4431   GET-SET       operation.
4432                 'get  = return previous property value (key definition)
4433                 'set  = copy definition once.
4434                 'sett = (force) copy definition even if already copied.
4435                 The 'set copies the key definition behind the propert
4436                 PROP only if there is no previous value. 'sett
4437                 replaces the content of PROPERTY.
4438   PROP          property name
4439   KEY           string -- key binding.
4440
4441 Examples:
4442
4443   (ti::keymap-bind-control 'mail-mode-map 'set 'my \"\\C-c\\C-c\")
4444   --> mail-send-and-exit, saved to property 'my
4445
4446   (ti::keymap-bind-control 'mail-mode-map 'set 'my \"\\C-c\\C-c\")
4447   --> nil, property 'my Was already set
4448
4449   (ti::keymap-bind-control 'mail-mode-map 'get 'my \"\\C-c\\C-c\")
4450   --> mail-send-and-exit, get the saved property 'my.
4451
4452 Live example:
4453
4454   ;; - first save original, then use our function. Use property
4455   ;;   'my, because The C-c C-c can already be occupied by
4456   ;;   some other package...
4457   ;; - it calls the original afterwards
4458
4459   (ti::keymap-bind-control 'mail-mode-map 'set 'my \"\\C-c\\C-c\")
4460   (define-key      mail-mode-map \"\\C-c\\C-c\" 'my-mail-func-CcCc)
4461
4462   (defun my-mail-func-CcCc (arg)
4463     ...
4464     (funcall  ;; Call the original.
4465       (ti::keymap-bind-control 'mail-mode-map 'get 'my \"\C-c\C-c\")
4466       arg)
4467     ;; Function ends here.)"
4468   (let* (map
4469          map-key
4470          sym
4471          val
4472          func)
4473     (unless (boundp map-symbol)
4474       (error "No variable bound %s" map))
4475     (setq map (eval map-symbol))
4476     (unless (keymapp map)
4477       (error "Not a keymap %s" map-symbol))
4478     (if (or (ti::nil-p key)             ;must be valid string
4479             (not (stringp key)))
4480         (error "Invalid KEY %s" key))
4481     (setq map-key (concat (symbol-name map-symbol) key))
4482     (setq func (lookup-key map key))
4483     (when func                          ;does function exist?
4484       (setq sym (intern map-key)
4485             val (get sym prop))
4486       (cond
4487        ((eq get-set 'get)
4488         val)
4489        ((and (eq get-set 'set)
4490              (null val))                ;set only if PROP not exist
4491         (put sym prop func))
4492        ((eq get-set 'sett)              ;replace value
4493         (put sym prop func))))))
4494
4495 ;;; ----------------------------------------------------------------------
4496 ;;; - What is an translate table?
4497 ;;; - Well; it says "if you press this key I give you this character back"
4498 ;;; - It is used for remapping the keys, but beware! In X envinronment,
4499 ;;;   where you can paste data between emacs, the translation gives
4500 ;;;   unpleasant results. Try pasting the _normal_ \ char from other
4501 ;;;   window to emacs that uses transltion presented in example below.
4502 ;;;   --> you get the | character pasted into Emacs
4503 ;;;
4504 (defun ti::keymap-translate-table (&optional arg)
4505   "Make new translate table.
4506
4507 Input ARG
4508
4509   'use      Start using the new table if the
4510             `keyboard-translate-table' if nil. Otherwise does nothing.
4511   'use-new  replace current table with fresh one
4512   nil       return new, default translate table.
4513
4514 Examples:
4515
4516     Switch these keys. Let's assume the \\ key is on top after this,
4517     since it is used more often in emacs.
4518
4519     (ti::keymap-translate-table 'use)
4520     (aset keyboard-translate-table ?\\| ?\\\\ )
4521     (aset keyboard-translate-table ?\\\\ ?\\| )
4522
4523 Return:
4524
4525   new translate table"
4526   (let ((index 0)
4527         (xlat-table (make-string 128 0)))
4528     (while (< index 128)                ;Generate the identity map.
4529       (aset xlat-table index index)
4530       (setq index (1+ index) ))
4531     (cond
4532      ((eq arg 'use-new)
4533       (setq keyboard-translate-table xlat-table))
4534      ((eq arg 'use)
4535       (and (null keyboard-translate-table)
4536            (setq keyboard-translate-table xlat-table)))
4537      (t))
4538     xlat-table))
4539
4540 ;;; ----------------------------------------------------------------------
4541 ;;; - For preventing Emacs to beep and disabling the normal keys
4542 ;;;   (for mail, gnus, ... )
4543 ;;;
4544 (defun ti::keymap-put-abc-map (map &optional func)
4545   "Put function `ignore' to abc key MAP, optionally put FUNC."
4546   (let* ((i             0)
4547          (func          (or func 'ignore))
4548          low
4549          up)
4550     (while (< i 27 )
4551       ;;  Set lowercase/upcase keys to nil
4552       (setq low (char-to-string (+ 65 i))
4553             up  (char-to-string (+ 97 i)))
4554       (define-key map low func)
4555       (define-key map  up func)
4556       (incf i))))
4557
4558 ;;; ----------------------------------------------------------------------
4559 ;;;
4560 (defun ti::keymap-put-map (map &optional func)
4561   "Put function `ignore' to a0 > x <128 key MAP, optionally put FUNC."
4562   (let* ((i             20)
4563          (func          (or func 'ignore)))
4564     (while (< i 128 )
4565       (define-key map (char-to-string i) func)
4566       (incf i))))
4567
4568 ;;; ----------------------------------------------------------------------
4569 ;;; - Mapping keysto functions easily.
4570 ;;;
4571 (defun ti::keymap-mapkeys (map-key-fun args)
4572   "Maps MAP-KEY-FUN to list of keys in ARGS.
4573
4574 Example:
4575   (mapkeys
4576    'global-set-key
4577    '([f1] 'hilit-rehighlight-buffer
4578      [f2] 'eval-defun
4579      [f3] 'repeat-complex-command))"
4580   (let (key
4581         func
4582         (i 0)
4583         (len (length args)))
4584     (if (eq 0 (% len 2)) nil
4585       (error "args not paired"))
4586     (while (< i len )
4587       (setq key (nth i args)   func (nth (1+ i) args)   i (+ 2 i) )
4588       (funcall map-key-fun key func))))
4589
4590 ;;}}}
4591 ;;{{{ (T)ext properties, faces
4592
4593 ;;; ........................................................... &faces ...
4594
4595 ;;; ----------------------------------------------------------------------
4596 ;;;
4597 (defun ti::buffer-text-properties-wipe  (&optional beg end)
4598   "Remove all, ie. literally all, text properten between BEG and END.
4599 BEG AND end defaults to whole buffer.
4600 Doesn't care about read only status of buffer."
4601   (interactive "r")
4602   (let (buffer-read-only
4603         (inhibit-read-only t))          ;allow read-only prop wipe out
4604     (set-text-properties
4605      (or beg  (point-min))
4606      (or end  (point-max))
4607      nil)))
4608
4609 ;;; ----------------------------------------------------------------------
4610 ;;; - During setting a different color to a face,
4611 ;;;   the color may be occupied and emacs halts with message
4612 ;;;
4613 ;;;     (error "X server cannot allocate color" "DarkSeaGreen3")
4614 ;;;
4615 ;;; - This function allows you to give several "try" choices,
4616 ;;;
4617 (defun ti::set-face-try-list (list face &optional attribute)
4618   "Try to assign color to face.
4619 The input is list of color names which are tried one by one.
4620 First one that succeeds is assigned. If color is occupied, tries
4621 next one. Doesn't signal any errors.
4622
4623 Input:
4624
4625   LIST          (\"color1\" \"color2\" ..) or single color string
4626   FACE          symbol. E.g. 'region
4627   ATTRIBUTE     symbol. Choices are 'fg and 'bg. Default is 'fg
4628
4629 Return:
4630
4631   color         color that was assigned
4632   nil           all tries failed"
4633   (let* (status)
4634     (or attribute
4635         (setq attribute 'fg))
4636     (dolist (color (ti::list-make list))
4637       (when (condition-case nil
4638                 (progn
4639                   (cond
4640                    ((eq attribute 'fg)
4641                     (set-face-foreground face color))
4642                    (t
4643                     (set-face-background face color)))
4644                   (setq status color)
4645                   t)
4646               (error
4647                ;; cannot set
4648                nil))
4649         ;; succesfull; stop the loop
4650         (return)))
4651     status))
4652
4653 ;;}}}
4654
4655 ;;{{{ misc: movement
4656
4657 ;;; ############################################################ &Misc ###
4658
4659 ;;; ----------------------------------------------------------------------
4660 ;;;
4661 (defsubst ti::buffer-forward-line (&optional count)
4662   "Move vertically lines down. If COUNT is negative, then up.
4663
4664 `forward-line' moves point always to the beginning
4665 of next line, and the elisp manual says not to use `next-line' in
4666 programs.
4667
4668 This function behaves exactly as `next-line'. If the next line is shorter
4669 it moves to the end of line."
4670   ;; (interactive "P")
4671   (let* ((col (current-column)))
4672     (and (null count) (setq count 1))   ;No arg given
4673     (forward-line count)
4674     (move-to-column col)))
4675
4676 ;;}}}
4677 ;;{{{ buffer: line handling , addings strings
4678
4679 ;;; ......................................................... &m-lines ...
4680
4681 ;;; ----------------------------------------------------------------------
4682 ;;;
4683 (defun ti::buffer-looking-at-one-space ()
4684   "Return non-nil if point is in the middle on one whitespcae.
4685 This is a position where there is only one tab or one space or point is
4686 followed by one newline. Similarly if point is at `point-min' and there is
4687 only one whitepace, or at `point-max' is preceded by one whitespace."
4688   (let* ((char-backward (if (not (bobp))
4689                             (preceding-char)))
4690          (char-forward (if (not (eobp))
4691                            (following-char))))
4692     ;;  Point-!-Here
4693     (cond
4694      ((and (null char-backward)
4695            (null char-forward))
4696       ;;  BOBPEOBP ie. empty buffer.
4697       nil)
4698      ((and char-backward
4699            char-forward)
4700       ;; char-!-char
4701       (and (not (string-match "[ \t\f\r\n]"
4702                               (char-to-string char-backward)))
4703            (string-match "[ \t\f\r\n]"
4704                          (char-to-string char-forward))
4705            ;;  What is the next character?
4706            (save-excursion
4707              (forward-char 1)
4708              (not (string-match "[ \t\f\r\n]"
4709                                 (char-to-string (following-char)))))))
4710      (t
4711       ;; BOBP-!-char
4712       ;; char-!-EOBP
4713       (string-match  "[ \t\f\r\n]"
4714                      (char-to-string
4715                       (if (eobp)
4716                           char-backward
4717                         char-forward)))))))
4718
4719 ;;; ----------------------------------------------------------------------
4720 ;;;
4721 (defun ti::buffer-surround-with-char (char)
4722   "Insert two same CHAR around a string near point.
4723 String is delimited by whitespace, although the function will do
4724 the right thing at beginning or end of a line, or of the buffer.
4725 If the char is one of a matching pair, do the right thing.
4726 Also makes a great gift."
4727   (interactive "cSurround with char: ")
4728   ;; hmm, ought to be able to do this with syntax tables?
4729   (let
4730       ((begchar char)
4731        (endchar char))
4732     (cond
4733      ((or (char= char ?{) (char= char ?}))
4734       (setq begchar ?{)
4735       (setq endchar ?}))
4736      ((or (char= char ?\() (char= char ?\)))
4737       (setq begchar ?\()
4738       (setq endchar ?\)))
4739      ((or (char= char ?<) (char= char ?>))
4740       (setq begchar ?<)
4741       (setq endchar ?>))
4742      ((or (char= char ?`) (char= char ?'))
4743       (setq begchar ?`)
4744       (setq endchar ?'))
4745      ((or (char= char ?[) (char= char ?]))
4746       (setq begchar ?[)
4747             (setq endchar ?])))
4748     (re-search-backward "^\\|\\s-" (point-min))
4749     (if (not (bolp))
4750         (re-search-forward "\\s-")
4751       (if (looking-at "\\s-") (re-search-forward "\\s-")))
4752     (insert-char begchar 1)
4753     (let ((opoint (point)))
4754       (if (re-search-forward "\\s-\\|\n" (point-max) t)
4755           (forward-char -1)
4756         (goto-char (point-max)))
4757       (insert-char endchar 1)
4758       (if (eq (point) (+ opoint 1))
4759           (forward-char -1)))))
4760
4761 ;;; ----------------------------------------------------------------------
4762 ;;;
4763 (defun ti::buffer-fill-region-spaces (beg end &optional column)
4764   "Fill region BEG END with spaces until COLUMN or 80.
4765 In picture mode paste/copying rectangles,
4766 it easiest if the area has spaces in every row up till
4767 column \"80\".
4768
4769 To return to 'ragged' text, use function `ti::buffer-trim-blanks'
4770
4771 Input:
4772   BEG           beginning of area, always line beginning
4773   END           end of area, always line end.
4774   COLUMN        the fill column. Defaults to 79, because 80 would
4775                 add annoying \\ marks at the end of line."
4776   (interactive "*r\nP")
4777   (let* ((column   (or column 79))
4778          (spaces   (make-string (+ 2 column) ?\ ))
4779          line
4780          len
4781          add)
4782     (save-restriction
4783       (narrow-to-region beg end)
4784       (untabify (point-min) (point-max)) ;very important !!
4785       (ti::pmin)
4786       (while (not (eobp))
4787         (setq line (ti::read-current-line)
4788               len  (length line)
4789               add  (- column len))
4790         (if (<= add 0)
4791             nil                         ;we can't touch this
4792           (end-of-line)
4793           (insert (substring spaces 1 add)))
4794         (forward-line 1)))))
4795
4796 ;;; ----------------------------------------------------------------------
4797 ;;; - This nice and elegant solution to get quotes around the words,
4798 ;;;   but someday it should be generalized to put ANYTHING around the word.
4799 ;;;
4800 (defun ti::buffer-quote-words-in-region (beg end)
4801   "This function quotes words in selected region BEG END."
4802   (interactive "r")
4803   (goto-char beg)
4804   (while (< (point) end)
4805     (kill-word 1)
4806     (insert (prin1-to-string (current-kill 0)))
4807     (setq end (+ end 2))
4808     (forward-word 1)
4809     (forward-word -1)))
4810
4811 ;;; ----------------------------------------------------------------------
4812 ;;; - E.g. if you want to decide "fast filling", you could check if any line
4813 ;;    is longer that fill-column.
4814 ;;;
4815 (defun ti::buffer-find-longer-line (beg end len)
4816   "Check BEG END if there exist line longer than LEN.
4817
4818 Return:
4819   point    beginning of line
4820   nil"
4821   (let* (pos)
4822     (save-excursion
4823       (goto-char (min beg end))
4824       (while (and (null pos)
4825                   (not(eobp))
4826                   (< (point) (max beg end)))
4827         (end-of-line)
4828         (if (<= (current-column) len)
4829             nil
4830           (beginning-of-line) (setq pos (point)) )
4831         (forward-line 1)))
4832     pos))
4833
4834 ;;; ----------------------------------------------------------------------
4835 ;;;
4836 (defun ti::buffer-scramble-region (beg end &optional char)
4837   "Scrables text BEG END with char so that it's not readable any more.
4838 Preserves words by substituting every [a-zA-Z] with optional CHAR."
4839   (interactive "r")
4840   (let* ((ch (if char                   ;pick the scramble char
4841                  (char-to-string char)
4842                "o")))
4843     (save-excursion
4844       (save-restriction                 ;preserve prev narrowing
4845         (narrow-to-region beg end)
4846         (ti::pmin)
4847         (ti::buffer-replace-regexp "[a-zA-Z]" 0 ch)))))
4848
4849 ;;; ----------------------------------------------------------------------
4850 ;;; - This function requires user input when RE-LOOK is given
4851 ;;; - This is aimed for lisp programs
4852 ;;;
4853 (defun ti::buffer-add-string-region (beg end str &optional re-look)
4854   "Add to region BEG END STR and optionally to lines matching RE-LOOK.
4855 You might use this as intend-region by adding more spaces to any
4856 vertical position, but most likely this is best function for
4857 commenting arbitrary blocks of code.
4858
4859 1) set mark to _exact_column_ where to add string
4860 2) move cursor to destination line, column does not matter.
4861
4862 If you want to add string to specific lines only, supply
4863 rex when you are asked for 'look for rex'. Remember that this
4864 rex will be used from that mark column to the end of line, so whole line
4865 is not looked. Here is one example:
4866
4867       *mark here
4868     ;;; triple comment
4869     ; single comment
4870
4871     ;;; another triplet
4872     *cursor here
4873
4874     ;;#; triple comment
4875     ; single comment
4876
4877     ;;#; another triplet
4878       ^^^^^^^^^^^^^^^^^^^^ --> the REX match area, note not incl. leading!
4879
4880 Note that the single ';' isn't matched, because the mark's column position
4881 is further away.
4882
4883 References:
4884
4885   Emacs 19.28 has almost similar function. Look
4886   `string-rectangle'. It does not overwrite existing text."
4887   (interactive "r\nsString to region :\nsLook for re :")
4888   (let* (col
4889          look)
4890     (if (ti::nil-p re-look)             ;reset
4891         (setq re-look nil))
4892     (if (ti::nil-p str)
4893         nil                             ;pass, nothing given
4894       (save-excursion
4895         ;;  Get true boundaries.
4896         ;;
4897         (goto-char (min beg end)) (setq col (current-column))
4898         (setq beg (line-beginning-position))
4899         (goto-char (max beg end)) (setq end (line-end-position))
4900         (save-restriction
4901           (narrow-to-region beg end) (ti::pmin)
4902           (while (not (eobp))
4903             (move-to-column col t)
4904             (setq look   (if (and re-look
4905                                   (eq (current-column) col))
4906                              (looking-at re-look)
4907                            t))
4908             (if look
4909                 (insert str))
4910             (forward-line 1)))))))
4911
4912 ;;}}}
4913
4914 ;;{{{ buffer: lists handling, sorting
4915
4916 ;;; ----------------------------------------------------------------------
4917 ;;; - The default sort-regexp-fields is too limited and awkward to use.
4918 ;;; - This one offers easy interface to 'sort'
4919 ;;;
4920 (defun ti::buffer-sort-regexp-fields (list level re &optional numeric reverse case)
4921   "Sort LIST of strings at subexpression LEVEL of RE.
4922 Sort can optionally be NUMERIC, REVERSE or CASE sensitive.
4923
4924 Return:
4925   sorted list."
4926   (let* ((clist (copy-list list)))      ;sort modifies it otw.
4927     (sort clist
4928           (function
4929            (lambda (l r &optional ret elt1 elt2)
4930              (cond
4931               ((not case)               ;not sensitive
4932                (setq l  (downcase l)    ;ignore case
4933                      r (downcase r))))
4934              ;;  read the maches from strings
4935              (setq elt1 (ti::string-match re level l)
4936                    elt2 (ti::string-match re level r))
4937              (cond
4938               ((not (and elt1 elt2))    ;make sure match happened
4939                (setq ret nil))
4940               (numeric
4941                (setq ret
4942                      (if reverse
4943                          (< (string-to-int elt2)  (string-to-int elt1))
4944                        (< (string-to-int elt1)  (string-to-int elt2)))))
4945               (t
4946                (setq ret
4947                      (if reverse
4948                          (string< elt2 elt1)
4949                        (string< elt1 elt2)))))
4950              ret)))
4951     clist))
4952
4953 ;;}}}
4954
4955 ;;{{{ misc: shell, exec, process
4956
4957 ;;; ......................................................... &process ...
4958 ;;; - Current "misc" category
4959
4960 ;;; ----------------------------------------------------------------------
4961 ;;; - This is great function to build up completions for login names...
4962 ;;; - I have 400 entries in passwd file and it's not very  fast.
4963 ;;; - You Should call this only once with regexp "." and put all the entries
4964 ;;;   into some variable. Use that variable for lookup.
4965 ;;;
4966 (defun ti::file-passwd-grep-user-alist (re &optional not-re passwd-alist)
4967   "Grep all login names, where person name match RE.
4968 The matches are gathered from `ti::var-passwd-buffer' and  if it does not
4969 exist, error is signaled.
4970
4971 If optional NOT-RE is string, it will be used after the RE match is done.
4972 It is used to exclude items.
4973
4974 If PASSWD-ALIST is given it will be used instead to gather needed
4975 information. It should be alist int he form returned by function
4976 `ti::file-passwd-build-alist'
4977
4978 Return:
4979   ((login  . user-name-entry) ..)"
4980   (let* ((passwd-buffer   ti::var-passwd-buffer)
4981          ;;  The name is 5th entry
4982          ;;  neva:I5KJd2C33dtMg:418:200:Max Neva,Houston Texas ...
4983          (passwd-re   "^\\([^:]+\\):[^:]+:[^:]+:[^:]+:\\([^:,]+\\)")
4984          alist
4985          line
4986          login
4987          person)
4988     (cond
4989      (passwd-alist
4990       ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ list ^^^
4991       ;; Hm, the loops are almost identical, but what the heck...
4992       (while passwd-alist
4993         (setq line (cdr (car passwd-alist)))
4994         ;; It's possible, that there is no "person" field, e.g.
4995         ;;     "lp:*:9:7::/usr/spool/lp:/bin/sh"
4996         ;;              |
4997         ;;              empty field
4998         ;;
4999         ;;  It's quicker to test 2 at the same time, and only then decode
5000         ;;  the field into parts
5001         (when (and  (string-match re line)
5002                     (string-match passwd-re line))
5003           (setq login  (match-string 1 line))
5004           (setq person (match-string 2 line))
5005           (when (and login person)
5006             (if (or (not (stringp not-re))
5007                     (and (stringp not-re)
5008                          (not (string-match not-re person))))
5009                 (push (cons login person) alist))))
5010         (pop passwd-alist)))
5011      (t
5012       ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ buffer ^^^
5013       (if (null (ti::set-buffer-safe passwd-buffer))
5014           (error "Passwd buffer does not exist"))
5015       (if (eq (point-min) (point-max))
5016           (error "Passwd buffer is empty."))
5017       (ti::pmin)
5018       (while (not (eobp))
5019         (setq line (ti::read-current-line))
5020         (when (and (string-match  re  line)
5021                    (looking-at    passwd-re))
5022           (setq login  (match-string 1))
5023           (setq person (match-string 2))
5024
5025           (if (null (and login person))
5026               nil
5027             (if (or (not (stringp not-re))
5028                     (and (stringp not-re)
5029                          (not (string-match not-re person))))
5030                 (push (cons login person) alist))))
5031         (forward-line 1))))
5032     alist))
5033
5034 ;;; ----------------------------------------------------------------------
5035 ;;; E.g. in HP-UX the command is this
5036 ;;; (my-read-passwd-entry "jaalto" "ypcat passwd")
5037 ;;;
5038 (defun ti::file-passwd-read-entry (&optional user cmd)
5039   "Return USER's password entry using Shell CMD.
5040
5041 If the password buffer's content is not empty, the CMD isn't called, instead
5042 the entry is searched from the buffer. This reduces overhead of calling
5043 processes every time function is invoked.
5044
5045 References:
5046   `ti::var-passwd-buffer'"
5047   (let* ( ;;  Permanent buffer, since launching process is expensive
5048          (user      (or user (user-login-name)))
5049          (re        (concat "^" user ":"))
5050          (buffer    (get-buffer-create ti::var-passwd-buffer))
5051          ret)
5052     (unwind-protect
5053         (with-current-buffer buffer
5054           (when (eq (point-min) (point-max)) ;No entries yet
5055             (if (null cmd)
5056                 (error "Need command to get the passwd file")
5057               (erase-buffer)
5058               (let ((list (split-string cmd)))
5059                 (apply 'call-process
5060                        (car list)
5061                        nil
5062                        (current-buffer)
5063                        nil
5064                        (cdr list)))))
5065           (ti::pmin)
5066           (if (re-search-forward re nil t)
5067               (setq ret (ti::read-current-line)))))
5068     ret))
5069
5070 ;;; ----------------------------------------------------------------------
5071 ;;;
5072 (defun ti::file-passwd-build-alist (cmd)
5073   "Build assoc list out of passwd table using CMD.
5074 Please note, that building a list takes some time, so call this
5075 only once per program. The CMD must be a command to retrieve
5076 contents of passwd file.
5077
5078 Note:
5079
5080     The performance of this function is not very good. Expect
5081     parsing 1000 users/15 seconds.
5082
5083 Return:
5084
5085     ((login . full-passwd-entry) ..)"
5086   (let* ((passwd-buffer   ti::var-passwd-buffer)
5087          alist
5088          line
5089          login)
5090     ;;  force loading passwd entries
5091     (ti::file-passwd-read-entry "SomeUser" cmd)
5092     (with-current-buffer passwd-buffer
5093       (ti::pmin)
5094       (while (not (eobp))
5095         (beginning-of-line)
5096         (setq line (buffer-substring
5097                     (point) (progn (end-of-line) (point))))
5098         ;; password entry looks like this, sometimes there may be garbage
5099         ;; after shell command like these two grep notes.
5100         ;;
5101         ;;   grep: can't open a
5102         ;;   grep: can't open tty
5103         ;;
5104         ;;   lm58817:x:23193:23193:Leena M{ki|:/home3/li7/lm58817:/bin/tcsh
5105         (when (setq login (ti::string-match "^[^:]+" 0 line))
5106           (setq alist (cons (cons login line) alist)))
5107         (forward-line 1)))
5108     alist))
5109
5110 ;;}}}
5111 ;;{{{ misc: function
5112
5113 ;;; ----------------------------------------------------------------------
5114 ;;;
5115 (defun ti::buffer-defun-function-name (&optional point)
5116   "Return possible function name.
5117 Starts searching backward form current or optional POINT.
5118 Be sure to be in right mode, so that right `beginning-of-defun' is used.
5119
5120 In Lisp, the current function can be found only if it is left flushed.
5121
5122 In C++, this will simply returns line portion, which it thinks
5123 contains function name.
5124
5125 In Perl, it is supposed that word following \"sub\" is function name.
5126
5127 Input:
5128   point     where to look
5129
5130 Return:
5131   nil
5132   string"
5133   (let* ((name      (symbol-name major-mode))
5134          (lisp-re   (concat
5135                      "def\\(un\\|subst\\|macro\\|advice\\|var\\|const\\)"
5136                      "[ \t]+\\([^ \t]+\\)"))
5137          line
5138          ret)
5139     (setq line (ti::read-current-line))
5140     (save-excursion
5141       (ignore-errors
5142         ;;  Now comes fun part...Ugh!
5143         (cond
5144          ((or (setq ret (ti::string-match lisp-re 2 line))
5145               (string-match "lisp" name))
5146           ;;  This beginning-of-defun only finds only left
5147           ;;  flushed FORMS
5148           ;;
5149           (or ret
5150               (progn
5151                 (beginning-of-defun) (setq line (ti::read-current-line))
5152                 (setq ret (ti::string-match lisp-re 2 line)))))
5153
5154          ((or (string-match "CC" name)
5155               (string-match "C++" name))
5156           (beginning-of-defun)
5157           ;; A nightmare...
5158           ;;
5159           ;; perAtom_c *
5160           ;; pMscCg_c::DecodeV7
5161           ;; ()
5162           ;; {
5163           ;;
5164           ;; perAtom_c *pMscCg_c::DecodeV7
5165           ;; ()
5166           ;; {
5167           ;; Try our best...
5168           ;;
5169           (search-backward "(")
5170           (beginning-of-line)
5171           (or (setq ret (ti::buffer-match "^[ \t]*\\([^ \t(]+\\)[ \t]*(" 1))
5172               (progn                    ;skip one line up
5173                 (forward-line -1)
5174                 (setq ret (ti::buffer-match "^[ \t]*\\([^\n(]+\\)" 1)))))
5175
5176          ((and (string-match "perl" name)
5177                (re-search-backward "^[ \t]*sub[ \t]+\\([^ \t]+\\)" nil t))
5178           (setq ret (match-string 1)))))
5179       ret)))
5180
5181 ;;}}}
5182 ;;{{{ file
5183
5184 ;;; ############################################################ &File ###
5185
5186 ;;; ----------------------------------------------------------------------
5187 ;;;
5188 (defsubst ti::file-days-old   (file)
5189   "Calculate how many days old the FILE is. This is approximation."
5190   (let ((now  (current-time))
5191         (file (nth 5 (file-attributes file))))
5192     (/ (ti::date-time-difference now file) 86400)))
5193
5194 ;;; ----------------------------------------------------------------------
5195 ;;;
5196 (defun ti::file-touch (file)
5197   "Touch FILE by updating time stamp. FILE is created if needed.
5198 Note: the filename is handed to the shell binary `touch'. Make sure the
5199 filename is understood by shell and does not contain meta characters."
5200   (if (not (file-exists-p file))
5201       (with-temp-buffer (write-region (point) (point) file))
5202     (let* ((touch (or (get 'ti::file-touch 'touch-binary)
5203                       (executable-find  "touch")
5204                       (error "`touch' binary not found."))))
5205       (put 'ti::file-touch 'touch-binary touch)
5206       (call-process touch nil nil nil (expand-file-name file)))))
5207
5208 ;;; ----------------------------------------------------------------------
5209 ;;;
5210 (defun ti::file-ange-completed-message (&rest args)
5211   "Default message after file has been loaded. Ignore ARGS."
5212   (message "Ange-ftp bg completed"))
5213
5214 ;;; ----------------------------------------------------------------------
5215 ;;; #todo:  Not quite what I want...
5216 ;;;
5217 (defun ti::file-ange-status (ange-ref)
5218   "Return status on ANGE-REF ftp buffer.
5219
5220 Return:
5221  'no-ange        if no ange buffer exists
5222  (..)            some ange status values"
5223   (let* ((ret   'no-ange)
5224          ange
5225          buffer
5226          host
5227          user
5228          proc
5229          line
5230          stat
5231          busy)
5232     (require 'ange-ftp)
5233     (setq ange  (ange-ftp-ftp-name ange-ref) ;crack addr
5234           host  (nth 0 ange)
5235           user  (nth 1 ange))
5236     (cond
5237      ((setq buffer (ti::buffer-find-ange-buffer user host))
5238       (if (null buffer) (setq buffer buffer)) ;XEmacs 19.14 Bytecomp silencer
5239       ;;  Create a new process if needed
5240       (setq proc (ange-ftp-get-process host user))
5241       ;;  The status value is valid only when process finishes.
5242       (save-excursion
5243         (set-buffer (process-buffer proc))
5244         (ti::pmax)
5245         (setq ret   ange-ftp-process-result
5246               line  (ti::read-current-line)
5247               stat  (ange-ftp-process-handle-line line proc)
5248               busy  ange-ftp-process-busy)
5249         ;; STAT
5250         ;; t     = skip message
5251         ;; ange-ftp-process-result-line = good
5252         ;; fatal, deletes process.
5253         (setq ret (list ret stat busy)))))
5254     ret))
5255
5256 ;;; ----------------------------------------------------------------------
5257 ;;; - an easy interface to ange ftp to get dingle file in bg.
5258 ;;; - this actually is a "macro" or toplevel func to the
5259 ;;;   ti::file-ange-file-handle
5260 ;;;
5261 (defun ti::file-ange-download-file (ange-ref download-dir &optional not-bg)
5262   "Download single file pointed by ANGE-REF in background to the DOWNLOAD-DIR.
5263
5264 Input:
5265
5266   ANGE-REF      /login@site:/dir/dir/file.xx
5267   DOWNLOAD-DIR  valid directory where to put the file.
5268   NOT-BG        if non-nil the ftp is done in foregroung.
5269
5270 Return:
5271
5272   nil           if job is done in background
5273   status        if in fg. Nil means failure."
5274
5275   (let* (ange
5276          host
5277          user
5278          dir
5279          file
5280          to-dir)
5281     (require 'ange-ftp)
5282     (setq ange          (ange-ftp-ftp-name ange-ref) ;crack addr
5283           host  (nth 0 ange)
5284           user  (nth 1 ange)
5285           dir   (file-name-directory (nth 2 ange))
5286           file  (file-name-nondirectory (nth 2 ange))
5287           to-dir (expand-file-name download-dir))
5288     (ti::file-ange-file-handle 'get user host dir to-dir (list file) not-bg)))
5289
5290 ;;; ----------------------------------------------------------------------
5291 ;;; - an easy interface to ange ftp to get/put wanted files
5292 ;;; #todo: sometimes ange hangs, rarely but... should check if
5293 ;;;        process is live somehow?
5294 ;;; #todo: check that no process is going in the buffer, so that it's
5295 ;;;        not called many times (overlapping).
5296 ;;;
5297 (defun ti::file-ange-file-handle
5298   (mode user host dir lcd file-list &optional not-bg msg-func)
5299   "Get files from remote or put files to remote site.
5300
5301 Important:
5302
5303   All directory names must be absolute
5304
5305 Input:
5306
5307   MODE          'put or 'get
5308   USER          login name when logging to site
5309   HOST          site name
5310   DIR           remote site directory
5311   LCD           download local dir
5312   FILE-LIST     files to get from/put to remote site
5313   NOT-BG        should we wait until ange is done?
5314                 nil = run on bg, non-nil = wait until done.
5315   MSG-FUNC      function to call after download completes. Should
5316                 contain &rest args parameter. See more in ange-ftp.el
5317 Return:
5318
5319   nil           always if NOT-BG is nil
5320   status        if NOT-BG is non-nil. Value nil means that session
5321                 failed."
5322   (let* ((func          (or msg-func 'ti::file-ange-completed-message))
5323          (max-try       5)
5324          (try           0)
5325          proc
5326          point
5327          ret)
5328     (require 'ange-ftp)
5329     (cond                               ;get commands
5330      ((eq mode 'get)
5331       (setq mode "mget"))
5332      ((eq mode 'put)
5333       (setq mode "mput")
5334       (setq func 'ignore))         ;can't use any function for this...
5335      (t
5336       (error "What mode?")))
5337     (if (not (ti::listp file-list))
5338         (error "file-list must be LIST and _not_ empty"))
5339     ;;  We need absolute directory names, because the FTP process
5340     ;;  running does not understand anything else.
5341     (setq lcd (expand-file-name lcd))
5342     ;;  Start FTP session if it does not exist
5343     ;;
5344     (setq proc (ange-ftp-get-process host user))
5345 ;;;    (setq M mode U user H host D dir L lcd F file-list P proc)
5346     ;;  - Expand remote site's directory reference
5347     (setq dir (ange-ftp-real-file-name-as-directory
5348                (ange-ftp-expand-dir host user dir)))
5349     ;;  Without this, the next command dies. This is already called in function
5350     ;;  ange-ftp-get-process, but for some unknown reason it must be called
5351     ;;  again to be sure: the hash mark size was sometimes nil
5352     (with-current-buffer (ange-ftp-ftp-process-buffer host user)
5353       (if (null ange-ftp-ascii-hash-mark-size)
5354           (setq ange-ftp-ascii-hash-mark-size 1024))
5355       (if (null ange-ftp-binary-hash-mark-size)
5356           (setq ange-ftp-binary-hash-mark-size 1024)))
5357     (ange-ftp-set-binary-mode host user)
5358     ;;  - After this commands ANGE hangs quite often and never executes
5359     ;;    the "raw" commands
5360     ;;  - That's why we loop MAX-TRY times to start the
5361     ;;    command.
5362     (ange-ftp-send-cmd host user (list 'lcd lcd) "Setting lcd...")
5363     (message "")
5364     ;;  CD command dies if it the directory is wrong
5365     ;;
5366     ;;  The socond command just makes sure the command was successfull.
5367     ;;  I added this, because when connection was cloased and ange
5368     ;;  opened the connection again, the CWD command didn't succeed
5369     ;;  right away. We must wait here until it succeeds and only then
5370     ;;  send the real put or get request.
5371     (ange-ftp-cd host user dir)
5372     (save-excursion
5373       (set-buffer (process-buffer proc))
5374       (setq try 0)
5375       (while
5376           (and (progn
5377                  (ti::pmax) (forward-line -1)
5378                  ;;  ftp> 250 CWD command successful.
5379                  (not (string-match "success" (ti::read-current-line))))
5380                (< try max-try))
5381         (incf try)))
5382     (push mode file-list)               ;command for ange
5383     (save-excursion
5384       (set-buffer (process-buffer proc))
5385       (ti::pmax)
5386       ;;  Try sending untill the point moves... => process started
5387       (setq point (point)   try 0)
5388       (while (and (eq point (point))
5389                   (< try max-try))
5390 ;;;     (ti::d! (eq point (point)) point (point))
5391         (ange-ftp-raw-send-cmd
5392          proc
5393          (ti::list-to-string file-list)
5394          "ftp ..."                  ;message displayed during 0%..100%
5395          (list func)                    ;called after completion ?
5396          (not not-bg))                  ;continue without wait
5397         (ti::pmax)
5398         (incf try)))
5399     ;;  The status value is valid only when process finishes.
5400     (if not-bg
5401         (save-excursion
5402           (set-buffer (process-buffer proc))
5403           (setq ret ange-ftp-process-result)))
5404     ret))
5405
5406 ;;; ----------------------------------------------------------------------
5407 ;;;
5408 (defun ti::file-chmod-w-toggle (file)
5409   "Toggle read-only flag for FILE.
5410 If file does not exist, or is not owned by user this function does nothing.
5411
5412 Return:
5413
5414   'w+    file made writable
5415   'w-    file made read-only.
5416   nil    file not processed."
5417   (let* ((file (expand-file-name file))
5418          mode)
5419     (when (ti::file-modify-p file)
5420       (setq mode (ti::file-toggle-read-write (file-modes file)))
5421       (set-file-modes file mode)
5422       ;;  return value , -r--r--r-- , 600 oct= 384 dec
5423       (if (= 0 (logand mode 128))
5424           'w-
5425         'w+))))
5426
5427 ;;; ----------------------------------------------------------------------
5428 ;;;
5429 (defun ti::file-chmod-make-writable (file)
5430   "Make FILE writable."
5431   (set-file-modes file (ti::file-mode-make-writable (file-modes file))))
5432
5433 ;;; ----------------------------------------------------------------------
5434 ;;;
5435 (defun ti::file-chmod-make-read-only (file)
5436   "Make FILE read only."
5437   (set-file-modes file (ti::file-mode-make-read-only (file-modes file))))
5438
5439 ;;; ----------------------------------------------------------------------
5440 ;;;
5441 (defun ti::file-find-shadows (&optional path)
5442   "Find duplicate files along optional PATH, which defaults to `load-path'."
5443   (interactive)
5444   (or path (setq path load-path))
5445   (save-excursion
5446     (let ((true-names   (mapcar 'file-truename path))
5447           (reduds       0)
5448           files dir
5449           out-buffer
5450           curr-files
5451           orig-dir
5452           files-seen-this-dir
5453           file
5454           d1
5455           d2)                           ;directories
5456       (while path
5457         (if (member (car true-names) (cdr true-names))
5458             (setq reduds (1+ reduds))
5459           (setq dir (car path))
5460           (setq curr-files (if (file-accessible-directory-p dir)
5461                                (directory-files dir nil ".\\.elc?$" t)))
5462           (and curr-files
5463                (interactive-p)
5464                (message "Checking %d files in %s..." (length curr-files) dir))
5465           (setq files-seen-this-dir nil)
5466           (while curr-files
5467             (setq file (car curr-files))
5468             (setq file (substring
5469                         file 0 (if (string= (substring file -1) "c") -4 -3)))
5470             (unless (member file files-seen-this-dir)
5471               (setq files-seen-this-dir (cons file files-seen-this-dir))
5472               (if (not (setq orig-dir (assoc file files)))
5473                   (setq files (cons (list file dir) files))
5474                 (if (null out-buffer)
5475                     (progn
5476                       (setq out-buffer (get-buffer-create "*Shadows*"))
5477                       (display-buffer out-buffer)
5478                       (set-buffer out-buffer)
5479                       (erase-buffer)))
5480                 ;; Do not print if directories are the same
5481                 ;; ++ [jari]
5482                 (setq d1 (file-name-as-directory (car (cdr orig-dir)))
5483                       d2 (file-name-as-directory dir))
5484                 (unless (string= d1 d2)
5485                   (insert
5486                    (format "%s%s shadows\n%s%s\n\n" d1 file d2 file)))))
5487             (setq curr-files (cdr curr-files)))) ;; if
5488         (setq path       (cdr path)
5489               true-names (cdr true-names)))
5490       (if (interactive-p)
5491           (let ((msg
5492                  (if out-buffer
5493                      (let ((n (/ (count-lines (point-min) (point-max)) 3)))
5494                        (format "%d shadowing%s found" n (if (eq n 1) "" "s")))
5495                    "No shadowings found")))
5496             (message "%s%s" msg
5497                      (if (zerop reduds) ""
5498                        (format " (skipped %d redundant entr%s in path)"
5499                                reduds (if (eq reduds 1) "y" "ies"))))))
5500       out-buffer)))
5501
5502 ;;; ----------------------------------------------------------------------
5503 ;;;
5504 (defun ti::directory-part-last (dir)
5505   "Return last portion of DIR.
5506 Like ~/this/dir/ would return `dir'.
5507 for `dir/' return `dir'."
5508   (when (or (string-match "^.*[\\/]\\([^\\/]+\\)[\\/]?$" dir)
5509             (string-match "^\\([^\\/]+\\)[\\/]?$" dir))
5510     (match-string 1 dir)))
5511
5512 ;;; ----------------------------------------------------------------------
5513 ;;;
5514 (defun ti::directory-unique-roots (path-list)
5515   "Return unique root directories of PATH-LIST.
5516 Non-strings or empty strings in PATH-LIST are ignored.
5517
5518 For example for directories ~/elisp/packages and ~/elisp/packages/more
5519 the unique root is ~/elisp/packages."
5520   (with-temp-buffer
5521     (dolist (path path-list)
5522       (when (and (stringp path)
5523                  (not (ti::nil-p path)))
5524         (insert (expand-file-name path) "\n")))
5525     (sort-lines nil (point-min) (point-max))
5526     (ti::pmin)
5527     (let (list
5528           line)
5529 ;;;      (pop-to-buffer (current-buffer)) (ti::d! 'starting)
5530       (while (not (eobp))
5531         (setq line (ti::buffer-read-line))
5532         (push line list)
5533         (beginning-of-line)
5534         (save-excursion
5535           (delete-matching-lines (concat "^" (regexp-quote line)))))
5536 ;;;      (ti::d! 'ok list)
5537       list)))
5538
5539 ;;; ----------------------------------------------------------------------
5540 ;;; (tinypath-subdirectory-list "~")
5541 ;;;
5542 (defun ti::directory-subdirectory-list (path)
5543   "Return all subdirectories under PATH."
5544   (let* (list)
5545     (dolist (elt (directory-files path 'absolute) )
5546       (when (and (not (string-match "\\.\\.?$" elt)) ;; skip . and ..
5547                  (file-directory-p elt)) ;; take only directories
5548         (push elt list)))
5549     list))
5550
5551 ;;; ----------------------------------------------------------------------
5552 ;;;
5553 (defun ti::directory-recursive-do (root function)
5554   "Start at ROOT and call FUNCTION recursively in each ascended directory."
5555   (let* ((list (ti::directory-subdirectory-list root)))
5556     (if (null list)
5557         (funcall function root)
5558       (dolist (path list)
5559         (ti::directory-recursive-do path function)))))
5560
5561 ;;; ----------------------------------------------------------------------
5562 ;;;
5563 (defun ti::directory-up (path)
5564   "Go one PATH directory level up.
5565
5566 Cygwin hpath handling:
5567
5568   /cygdrive/            => /              May not be what you want
5569   /cygdrive/c/          => /cygdrive/c    Can't go no more upward
5570   /cygdrive/c/tmp       => /cygdrive/c
5571
5572 Dos path handling:
5573
5574   c:/temp               => d:/            Notice, cannot return \"d:\"
5575
5576 Unix path handling:
5577
5578   /path1/path2          => /path1
5579   /path1/path2/         => /path1
5580   /path1/path2/file.txt => /path1/path2"
5581   (cond
5582    ((string-match "^/$\\|^[a-z]:[\\/]?$" path)
5583     path)
5584    (t
5585     (if (string-match "[/\\]$" path)
5586         (setq path (ti::string-match "^\\(.*\\)[^\\/]+" 1 path)))
5587     ;; /cygdrive/c/  is already a root directory
5588     (cond
5589      ((string-match "^\\(/cygdrive/.\\)/?$" path)
5590       (match-string 1 path))
5591      (t
5592       (setq path (file-name-directory path))
5593       ;;  d:/temp  => d:/   ,do not return "d:"
5594       (if (and (string-match "[/\\].+[/\\]" path)
5595                (string-match "^\\([a-z]:\\)?.+[^/\\]" path))
5596           (match-string 0 path)
5597         path))))))
5598
5599 ;;; ----------------------------------------------------------------------
5600 ;;;
5601 (defun ti::directory-subdirs (dir)
5602   "Return directories under DIR."
5603   (let* (list)
5604     (when (file-directory-p dir)
5605       (dolist (elt (directory-files dir 'full))
5606         (if (file-directory-p elt)
5607             (push elt list))))
5608     list))
5609
5610 ;;; ----------------------------------------------------------------------
5611 ;;;
5612 (defun ti::directory-unix-man-path-root ()
5613   "Determine manual page root path. "
5614   (let (root)
5615     (dolist (try '("/opt/local/man"     ;HP-UX new
5616                    "/usr/share/man"     ;HP old
5617                    "/usr/man"))         ;Sun and Linux
5618       (if (ti::win32-cygwin-p)
5619           (setq try (w32-cygwin-path-to-dos try)))
5620       (when (and try
5621                  (file-directory-p try))
5622         (return try)))))
5623
5624 ;;; ----------------------------------------------------------------------
5625 ;;;
5626 (defun ti::directory-files (dir re &optional absolute form not-re-form)
5627   "Return files from DIR.
5628
5629 Input:
5630
5631   DIR           directory name
5632   RE            regexp for files to match
5633   ABSOLUTE      flag, Return files as absolute names?
5634   FORM          eval form, test each file with FORM instead of RE
5635   NOT-RE-FORM   eval form, drop file if this evaluates to t
5636
5637 Example:
5638
5639   ;;  Get all filenames that aren't zipped, backups or objects.
5640   ;;  The 'arg' will hold the filename
5641
5642   (ti::directory-files dir re t nil '(string-match \"g?[Z~#o]$\" arg)))
5643
5644   ;; Return only directory names
5645
5646   (ti::directory-files dir \".\" 'absolute
5647                    '(file-directory-p arg)
5648                    '(string-match \"\\\\.\\\\.?$\" arg))
5649
5650 Return:
5651
5652   list          (file file file ..)"
5653   (let* (ret)
5654     (dolist (arg
5655              (directory-files dir absolute re))
5656       (when (or (null form)             ;accept all
5657                 (eval form))            ;accept only these
5658         (when (or (null not-re-form)
5659                   (null (eval not-re-form)))
5660           (push arg ret ))))
5661     (nreverse ret)))
5662
5663 ;;; ----------------------------------------------------------------------
5664 ;;;
5665 ;;;
5666 (defun ti::file-files-only (list &optional eval-form)
5667   "Return existing files. Drop directories from LIST of strings.
5668 Note: 200 files takes about 2-3 secs. If you supply EVAL-FORM, the item
5669 will be included if the form Return t. You can refer to current item
5670 with symbol 'arg'.
5671
5672 Input:
5673
5674   LIST          list of strings
5675   EVAL-FORM     optional eval statement
5676
5677 Return:
5678  (file ..)     list"
5679   (let* (ret)
5680     (dolist (arg list)
5681       (if (if eval-form
5682               (eval eval-form)
5683             (and (file-exists-p arg)
5684                  (not (file-directory-p arg))))
5685           (push arg ret)))
5686     (nreverse ret)))
5687
5688 ;;; ----------------------------------------------------------------------
5689 ;;;
5690 (defun ti::file-newer-exist (f1 f2)
5691   "Return file F1 or F2 which is newer. If only one of them exist, return it.
5692
5693 Return:
5694   str   file
5695   nil   none of them exist"
5696   (cond
5697    ((and (file-exists-p f1)
5698          (file-exists-p f2))
5699     (if  (file-newer-than-file-p f1 f2)
5700         f1 f2))
5701    ((file-exists-p f1)
5702     f1)
5703    ((file-exists-p f2)
5704     f1)
5705    (t
5706     nil)))
5707
5708 ;;; ----------------------------------------------------------------------
5709 ;;;
5710 (defun ti::file-get-extension (file &optional mode)
5711   "Return FILE extension.
5712 If MODE is nil, then return nil if none exist,
5713 if MODE is non-nil, return empty string instead."
5714   (let* (list
5715          ext
5716          len)
5717 ;;;    (ti::d! (null file) (null (string-match "\\." file)))
5718     (if (or (null file)
5719             (null (string-match "\\." file)))
5720         nil
5721       (setq list  (split-string file "[\.]"))
5722       (setq len   (length list))
5723       (if (eq 1 len)
5724           (setq ext (car list))          ; first element
5725         (setq ext (nth (1- len) list)))) ; last element
5726     (if ext ext                          ;what to return?
5727       (if mode
5728           ""
5729         nil))))
5730
5731 ;;; ----------------------------------------------------------------------
5732 ;;;
5733 (defun ti::file-path-and-line-info  (path)
5734   "Return (PATH . LINE-NBR) if path is in format PATH:NBR."
5735   (let* (line)
5736     (when (string-match ":\\([0-9]+\\):?[ \t\f]*$" path)
5737       (setq line (string-to-int (match-string 1 path)))
5738       (setq path (ti::replace-match 0 "" path))
5739       (cons path line))))
5740
5741 ;;; ----------------------------------------------------------------------
5742 ;;;
5743 (defsubst ti::file-path-to-unix (path)
5744   "Convert PATH to Unix forward slash format."
5745   (replace-char-in-string ?/ ?\\  path))
5746
5747 ;;; ----------------------------------------------------------------------
5748 ;;;
5749 (defsubst ti::file-path-to-msdos (path)
5750   "Convert PATH to MS-DOS backward slash format."
5751   (replace-char-in-string ?\\ ?/  path))
5752
5753 ;;; ----------------------------------------------------------------------
5754 ;;;
5755 (defun ti::file-make-path  (dir &optional file)
5756   "Make full path by combining DIR and FILE.
5757 In Win32, return backward slashed paths. Otherwise forward slashed
5758 paths.
5759
5760 DIR will always have trailing directory separator.
5761 You need to call this function if you pass a absolute path to
5762 external processes. Emacs in the other hand can handle both \\ and /
5763 internally."
5764   (if (ti::emacs-type-win32-p)
5765       (replace-char-in-string
5766        ?\\ ?/  (concat (file-name-as-directory dir) (or file "")))
5767     (replace-char-in-string
5768      ?/ ?\\  (concat (file-name-as-directory dir) (or file "")))))
5769
5770 ;;; ----------------------------------------------------------------------
5771 ;;; #defalias (defalias 'which 'ti::file-get-load-path)
5772 ;;;
5773 (defun ti::file-get-load-path (fn paths &optional all-paths verb)
5774   "Return full path name for FN accross the PATHS.
5775 Input:
5776
5777   FN            filename to search
5778   PATHS         list of path names
5779   ALL-PATHS     return all matches.
5780   VERB          verbose flag. Allows printing values in echo area
5781
5782 Return:
5783
5784   nil           no matches
5785   str           first match if all-paths is nil
5786   list          list of matches along paths."
5787   (interactive
5788    (let* ((map (copy-keymap minibuffer-local-map))
5789           var1
5790           var2)
5791      (define-key map "\t"   'lisp-complete-symbol)
5792      (define-key map "\C-m" 'exit-minibuffer)
5793      (setq var1 (read-from-minibuffer "sFile: "))
5794      (setq var2 (read-from-minibuffer "Lisp var: " "exec-path" map))
5795      (list var1 (eval (intern-soft var2)))))
5796   (let (file found)
5797     (ti::verb)
5798     (dolist (elt paths)
5799       (when (stringp elt)           ;you never know what's in there...
5800         (setq file (ti::file-make-path elt fn))
5801         (when (and (file-exists-p file)
5802                    (not (file-directory-p file)))
5803           (if all-paths
5804               (push file found)
5805             (setq  found file)
5806             (return)))))
5807     (if (and found all-paths)           ;preserve order
5808         (setq found (nreverse found)))
5809     (if (and found verb)
5810         (message (prin1-to-string found)))
5811     found))
5812
5813 ;;; ----------------------------------------------------------------------
5814 ;;;
5815 (defun ti::file-user-home ()
5816   "Try to guess user's home directory.
5817
5818 Return:
5819    /PATH/PATH/USER/    users home
5820    nil                 not found"
5821   (let* ((usr       (or (getenv "USER") (getenv "LOGNAME") ))
5822          (home      (or (getenv "HOME") (getenv "home") ))
5823          (path      (expand-file-name "~")))
5824     (cond
5825      (path)
5826      ((> (length home) 0)               ;$HOME exist
5827       (setq path home))
5828      ((> (length usr) 0)                ;users name exist
5829       (with-temp-buffer
5830         (cond
5831          ((executable-find "pwd")       ;Win32 test
5832           ;;   Try to get via 'pwd' process then.
5833           (call-process "pwd" nil (current-buffer) nil)
5834           (ti::pmin)
5835           (if (re-search-forward usr nil t)
5836               (setq path (buffer-substring (point-min) (match-end 0)))))
5837          ((executable-find "ls")
5838           ;;  Failed ? try ls then...
5839           (erase-buffer)
5840           (call-process "ls" nil (current-buffer) nil)
5841           (if (re-search-forward usr nil t)
5842               (setq path (buffer-substring
5843                           (point-min) (match-end 0)))))))))
5844     ;;  make sure it has  trailing "/"
5845     (and (stringp path)
5846          (setq path (ti::file-make-path path)))
5847     path))
5848
5849 ;;; ----------------------------------------------------------------------
5850 ;;; You can use this in interactive command to build up a completion list:
5851 ;;; like this:
5852 ;;;
5853 ;;;  (interactive
5854 ;;;     (list (completing-read
5855 ;;;             "Visit file: " (ti::file-file-list load-path "\\.el$"))))
5856 ;;;   (let ((pair (assoc emacs-file (ti::file-file-list load-path "\\.el$"))))
5857 ;;;     (if pair
5858 ;;;             (find-file (cdr pair))
5859 ;;;       (find-file (expand-file-name emacs-file "~/emacs")))))
5860 ;;;
5861 (defun ti::file-file-list (dirs re)
5862   "Read DIRS and return assoc of files matching RE. (FILE FULL-PATH-FILE)."
5863   (let ((files nil))
5864     (and (stringp dirs)                 ;only one entry given ?
5865          (setq dirs (list dirs)))
5866     (while dirs
5867       (setq files
5868             (append files (directory-files (car dirs) t re)))
5869       (setq dirs (cdr dirs)))
5870     (mapcar
5871      (function
5872       (lambda (file)
5873         (cons (file-name-nondirectory file) file))
5874       files))))
5875
5876 ;;; ----------------------------------------------------------------------
5877 ;;;
5878 (defun ti::file-complete-file-name (file-name &optional dir flist)
5879   "Given a FILE-NAME string return the completed file name.
5880
5881 Input:
5882
5883   If FILE-NAME is invalid entry, signal no error and return nil
5884   If no DIR is not given, use FILE-NAME's directory.
5885   If no DIR nor FILE-NAME dir, use `default-directory'
5886   if non-nil flag FLIST, then return completed filename list
5887
5888 Nots:
5889
5890   DIR must end to a slash or otherwise it is considered partial
5891   filename.
5892
5893 Return:
5894
5895   str           full completion
5896   list          list of completions if FLIST is set.
5897   nil           not unique"
5898   (let* ((type     (cond
5899                     ((and (ti::win32-p)
5900                           (ti::emacs-type-win32-p)
5901                           (string-match "/cygdrive" file-name))
5902                      'cygwin)
5903                     (t
5904                      'emacs)))
5905          (file         (substitute-in-file-name file-name))
5906          (uncomplete   (file-name-nondirectory file))
5907          odir
5908          completed)
5909     (setq odir                          ;Save the original directory.
5910           (substring file-name 0 (- (length file-name) (length uncomplete))))
5911     ;;  Relative path
5912     (if (and (stringp odir)
5913              (stringp dir)
5914              (string-match "^\\.\\." odir))
5915         (setq dir (format "%s%s" (file-name-as-directory dir) odir)))
5916     ;;  expand-file-name dies if default-directory is nil
5917     (setq dir
5918           (expand-file-name
5919            (or dir
5920                (file-name-directory file-name)
5921                default-directory
5922                "~")))
5923     (setq completed
5924           ;;   if given impossible entry like "!@#!#"
5925           (ignore-errors
5926             (file-name-all-completions uncomplete dir)))
5927     ;; Only one match in the list? voila!
5928     (if (and completed
5929              (eq 1 (length completed)))
5930         (setq completed (ti::file-name-forward-slashes (car completed))))
5931     (cond
5932      ((and (stringp completed)
5933            (not (string= completed uncomplete)))
5934       (concat odir completed))
5935      ((and flist completed)
5936       completed))))
5937
5938 ;;; ----------------------------------------------------------------------
5939 ;;;
5940 (defun ti::file-complete-file-name-word (&optional word no-msg)
5941   "Complete filename WORD at point.
5942 `default-directory' is used if no directory part in filename.
5943 See `ti::file-complete-file-name'.
5944
5945 You can use this feature easily in Lisp interactive call.
5946 See macro `ti::file-complete-filename-minibuffer-macro' for more.
5947
5948 NO-MSG  if non-nil, do not flash possible choices at current point
5949         The `sit-for' command is used for displaying, so you can
5950         interrupt it by pressing any key."
5951   (interactive "P")
5952   (or word
5953       (setq word
5954             (save-excursion
5955               (forward-char -1)
5956               (ti::buffer-read-space-word))))
5957   (let* ((oword  word)
5958          (enable-recursive-minibuffers t)
5959          all
5960          tmp
5961          dir
5962          msg)
5963     ;;  expand-file-name dies if default-directory is nil
5964     (or default-directory
5965         (error "default-directory is nil !!"))
5966     (unless (ti::nil-p word)
5967       (setq word (ti::file-complete-file-name word nil 'list))
5968       (when (ti::listp word)
5969         (let ((alist (ti::list-to-assoc-menu word)))
5970           (when (stringp (setq tmp (try-completion oword alist)))
5971             (setq word tmp
5972                   ;; still completions left? Was this unique?
5973                   all  (all-completions word alist)))))
5974       (when (stringp word)
5975         (when (and (null no-msg)
5976                    ;;  This completion is not unique, so show all matches
5977                    (string= oword word)
5978                    (ti::listp all))
5979           (setq msg (format "%d: %s"
5980                             (length all)
5981                             (ti::list-to-string all)))
5982           (message msg)
5983           (sit-for 0.5)))
5984       (when (and (stringp word)
5985                  (not (string= word oword)))
5986         (skip-chars-backward "^\n\t ")
5987         (let ((point (point)))
5988           (skip-chars-forward "^\n\t ")
5989           (delete-region point (point))
5990           (insert (ti::file-name-forward-slashes word)))))))
5991
5992 ;;; ----------------------------------------------------------------------
5993 ;;;
5994 (put 'ti::file-complete-filename-minibuffer-macro 'lisp-indent-function 0)
5995 (defmacro ti::file-complete-filename-minibuffer-macro (&rest body)
5996   "Complete filename in minibuffer and do BODY.
5997 Use variable 'map' to pass map to `read-from-minibuffer' function.
5998
5999 Example call:
6000
6001   (ti::file-complete-filename-minibuffer-macro
6002     (read-from-minibuffer \"test\" nil map))
6003
6004 Example 2:
6005
6006     (defun my-example (string file-list)
6007       \"FILE-LIST is string. Allow completion on words\"
6008       (interactive
6009        (list
6010         (read-from-minibuffer \"Gimme string: \")
6011         (split-string
6012          (ti::file-complete-filename-minibuffer-macro
6013            (read-from-minibuffer \"Gimme file-list: \" nil map)))))
6014       (list string file-list))
6015
6016     (setq result (call-interactively 'my-example)) \"test\" RET <files> RET
6017     result
6018     --> (\"test\" (\"~/\" \"~/bin\" \"~/exe/\"))"
6019   (`
6020    (let* ((map (copy-keymap minibuffer-local-map)))
6021      ;;  this event also exists for tab
6022      (define-key map [kp-tab]   'ti::file-complete-file-name-word)
6023      (define-key map [tab]      'ti::file-complete-file-name-word)
6024      (define-key map "\t"       'ti::file-complete-file-name-word)
6025      (,@ body))))
6026
6027 ;;; ----------------------------------------------------------------------
6028 ;;;
6029 (defun ti::file-read-file-list (&optional message)
6030   "Read file or directory list as one string, and return it as LIST.
6031 Display optional MESSAGE, otherwise use default message.
6032
6033 Filesnames can be completed with tab. `default-directory' is used for
6034 files that do not have directory part. Make sure default dir has ending
6035 slash.
6036
6037 Example:
6038
6039   (setq files (mapcar 'expand-file-name (ti::file-read-file-list)))
6040
6041 Return:
6042
6043   (ELT ELT ..)          with `default-directory'
6044   nil                   no input"
6045   (let* (list
6046          str)
6047     (setq str
6048           (ti::file-complete-filename-minibuffer-macro
6049             (read-from-minibuffer
6050              (or message (format
6051                           "...%s: "
6052                           ;; limit the directory name
6053                           (ti::string-right default-directory 10)))
6054              nil map)))
6055     (unless (ti::nil-p str)             ;not empty?
6056       (dolist (str (split-string str " "))
6057         (if (not (string-match "/" str))
6058             (setq str (concat default-directory str)))
6059         (push str list)))
6060     (nreverse list)))
6061
6062 ;;}}}
6063
6064 ;;{{{ Network streams
6065
6066 ;;; ......................................................... &network ...
6067
6068 ;;; ----------------------------------------------------------------------
6069 ;;;
6070 (defun ti::process-finger-error (&optional buffer)
6071   "Read BUFFER containing a finger response after `ti::process-finger'.
6072 If there is an error, then return possible error cause string.
6073
6074 Return:
6075  string     cause of error
6076  nil        no error"
6077   (let* (ret)
6078     (with-current-buffer (or buffer (current-buffer))
6079       (ti::pmin)
6080       (when (re-search-forward "unknown host:" nil t)
6081         (setq ret (ti::read-current-line))))
6082     ret))
6083
6084 ;;; ----------------------------------------------------------------------
6085 ;;; Original function in   mc-pgp.el:mc-pgp-fetch-from-finger
6086 ;;;
6087 (defun ti::process-finger (email &optional port timeout buffer verb)
6088   "Finger EMAIL on PORT with TIMEOUT.
6089 The output is clered from possible ^M characters.
6090
6091 Input:
6092
6093   EMAIL             email address foo@site.com
6094   PORT              default is 79
6095   TIME              default is 25
6096   BUFFER            where to store result, default is *finger tmp*
6097   VERB              print verbose messages
6098
6099 Return:
6100
6101   string            error while doing opening network stream
6102   buffer-pointer"
6103   (interactive "sFiger email: ")
6104   (let (connection
6105         user
6106         host
6107         ret)
6108     (setq verb      (or verb (interactive-p))
6109           port      (or port 79)
6110           timeout   (or timeout 25))
6111     (if (not (string-match "^\\([^ \t]+\\)@\\([^[ \t]+\\)" email))
6112         (error "Need email address foo@site.com '%s'" email)
6113       (setq user (match-string 1 email)
6114             host (match-string 2 email))
6115       (save-excursion
6116         (unwind-protect
6117             (progn
6118               (if verb     (message "Fingering %s ..." email))
6119               (setq buffer (or buffer (ti::temp-buffer "*finger tmp*" 'clear)))
6120 ;;;           (pop-to-buffer buffer) (ti::d! "going finger....")
6121               (condition-case error
6122                   (progn
6123                     (setq
6124                      connection
6125                      (open-network-stream "*finger*" buffer host port))
6126                     (process-send-string
6127                      connection (concat "/W " user "\r\n"))
6128                     (while (and (memq  (process-status connection) '(open))
6129                                 (accept-process-output connection timeout))))
6130                 (file-error
6131                  ;; '(file-error "connection refused "connection failed" ..)
6132                  (setq ret (ti::list-to-string (cdr error))))
6133                 (error
6134                  (setq ret (ti::list-to-string (cdr error)))))
6135               (if connection (delete-process connection))
6136               ;;  Strip Ctrl-M marks
6137               (with-current-buffer buffer
6138                 (ti::buffer-lf-to-crlf 'dos2unix)))))
6139       (when verb
6140         (message "Fingering %s ...done" email))
6141       (if (interactive-p)
6142           (pop-to-buffer buffer))
6143       (if connection
6144           buffer ret))))
6145
6146 ;;; ----------------------------------------------------------------------
6147 ;;;
6148 (defun ti::process-http-request (command &optional port timeout buffer verb)
6149   "Send http COMMAND i.e. URL request.
6150 Control character C-m is removed from response.
6151
6152 If COMMAND includes port number, e.g.:
6153
6154   http://www-swiss.ai.mit.edu:80/htbin/pks-extract-key.pl
6155
6156 This is actually intepreted as
6157
6158   http    = www-swiss.ai.mit.edu
6159   port    = 80
6160   command = /htbin/pks-extract-key.pl
6161
6162 Input:
6163
6164   COMMAND       http command string
6165   PORT          default is 80
6166   TIMEOUT       default is 60
6167   BUFFER        where to store result, default is *finger tmp*
6168   VERB          print verbose messages
6169
6170 Return:
6171
6172  '(buffer-pointer  error-string)
6173
6174   error-string      network stream error message.
6175   buffer            HTTP response."
6176   (interactive "sHttp request: ")
6177   (let (connection
6178         host
6179         ret)
6180     (setq verb      (or verb (interactive-p))
6181           port      (or port 80)
6182           timeout   (or timeout 60))
6183     (if (not (string-match "^http://\\([^/]+\\)\\(/.*\\)" command))
6184         (error "Must be _http_ request '%s'" command)
6185       (setq host    (match-string 1 command)
6186             command (match-string 2 command))
6187       (if (string-match "\\(.*\\):\\([0-9]+\\)" host)
6188           (setq port (string-to-int (match-string 2 host))
6189                 host (match-string 1 host))))
6190 ;;;   (ti::d!! "\n" command "HOST" host "PORT" port "TIME" timeout buffer)
6191     (save-excursion
6192       (unwind-protect
6193           (progn
6194             (when verb
6195               (message "Http %s ..." host))
6196             (setq buffer (or buffer (ti::temp-buffer "*http tmp*" 'clear)))
6197 ;;;         (ti::d! host port command "sending http....")
6198             (condition-case error
6199                 (progn
6200                   (setq
6201                    connection
6202                    (open-network-stream "*http*" buffer host port))
6203                   (process-send-string
6204                    connection
6205                    (concat "GET "
6206                            command
6207                            " HTTP/1.0\r\n\r\n"))
6208                   (while (and (eq 'open (process-status connection))
6209                               (accept-process-output connection timeout))))
6210               (file-error
6211                ;; '(file-error "connection refused "connection failed" ..)
6212                (setq ret (ti::list-to-string (cdr error))))
6213               (error
6214                (setq ret (ti::list-to-string (cdr error))))))
6215         ;; ................................................... cleanup ...
6216         (if connection
6217             (delete-process connection))
6218         ;;  Strip Ctrl-M marks
6219         (with-current-buffer buffer
6220           (ti::buffer-lf-to-crlf 'dos2unix))))
6221     (when verb
6222       (message "Http %s ...done" host))
6223     (if (interactive-p)
6224         (pop-to-buffer buffer))
6225     (list buffer ret)))
6226
6227 ;;}}}
6228 ;;{{{ shell: zipping
6229
6230 ;;; ....................................................... &shell-zip ...
6231
6232 ;;; ----------------------------------------------------------------------
6233 ;;;
6234 (defun ti::process-uname ()
6235   "Call `uname -a'."
6236   (let* ((uname (executable-find "uname")))
6237     (when uname
6238       (with-temp-buffer
6239         (call-process uname nil (current-buffer) nil "-a")
6240         (buffer-string)))))
6241
6242 ;;; ----------------------------------------------------------------------
6243 ;;;
6244 ;;; #todo
6245 ;;; #not tested
6246 ;;;
6247 (defun ti::process-zip (zip-file files &optional zip-cmd)
6248   "Achive to ZIP-FILE. FILES is list (file file ..).
6249 The ZIP-CMD defaults to \"zip -9 -q\",
6250 Command will not return until the process has finished."
6251   (let* ((zcmd          (or zip-cmd "zip -9 -q "))
6252          (shell-buffer  (get-buffer-create "*Shell output*"))
6253          (flist         (ti::list-join files))
6254          (cmd           (concat zcmd " " zip-file " " flist)))
6255     (call-process cmd nil shell-buffer)
6256     (if (interactive-p)
6257         (display-buffer shell-buffer))
6258     shell-buffer))
6259
6260 ;;; ----------------------------------------------------------------------
6261 ;;;
6262 (defun ti::process-zip-view-command (file &optional buffer nice zip-cmd verb)
6263   "Insert zip file listing to point.
6264
6265 Input:
6266
6267   FILE      tar file
6268   BUFFER    defaults to current buffer
6269   NICE      if non-nil, insert file name and empty lines around listing.
6270   ZIP-CMD   defaults to 'unzip -v %s'
6271   VERB      verbose mode
6272
6273 Return:
6274
6275   nil       no action [file not exist ...]
6276   nbr       shell return code"
6277   (interactive "fTar file: ")
6278   (let* ((cmd (or zip-cmd "unzip -v %s")))
6279     (ti::verb)
6280     (if (not (and (stringp file)
6281                   (file-exists-p file)))
6282         (error "Invalid file argument")
6283       (if nice
6284           (insert "file " (file-name-nondirectory file) ":\n"))
6285       (call-process cmd nil (or buffer (current-buffer)) nil
6286                     (expand-file-name file))
6287       (if nice
6288           (insert "\n")))))
6289
6290 ;;; ----------------------------------------------------------------------
6291 ;;;
6292 (defun ti::process-tar-zip-view-maybe-command (file)
6293   "If FILE is zip/tar then insert listing to current point."
6294   (cond
6295    ((string-match "\\.tar$\\|\\.tar.gz$\\|\\.tgz$" file)
6296     (ti::process-tar-view-command file nil 'nice))
6297    ((string-match "\\.zip$" file)
6298     (ti::process-zip-view-command file nil 'nice))))
6299
6300 ;;; ----------------------------------------------------------------------
6301 ;;;
6302 (put 'ti::process-perl-process-environment-macro 'lisp-indent-function 1)
6303 (put 'ti::process-perl-process-environment-macro 'edebug-form-spec '(body))
6304 (defmacro ti::process-perl-process-environment-macro (perl-type &rest body)
6305   "Check PERL-TYPE and run BODY in correct Win32/Cygwin environment.
6306 Fixe TEMP variable during the process call.
6307
6308 Input:
6309
6310   PERL-TYPE   'perl 'win32-cygwin 'win32-activestate
6311   BODY        Code to run."
6312   (`
6313    (let ((process-environment process-environment) ;; Make a local copy
6314          new)
6315      (dolist (elt process-environment)
6316        (cond
6317         ((string-match "^TEMP=\\(.*\\)" elt)
6318          (let* ((tmp-dir (match-string 1 elt))
6319                 (dir     (if (and (stringp tmp-dir)
6320                                   (file-directory-p tmp-dir))
6321                              (expand-file-name tmp-dir))))
6322            (cond
6323             ((and (ti::win32-shell-p)
6324                   ;;  c:\temp  or \\server\temp
6325                   (not (string-match "=[a-z]:[\\]\\|=[\\][\\][a-z]" elt)))
6326              (if (file-directory-p "C:/TEMP")
6327                  (push "TEMP=C:\\TEMP" new)
6328                (push "TEMP=C:\\" new)))
6329             ((and (string-match "[\\]\\|[a-z]:" tmp-dir) ;; Dos path
6330                   (not (eq perl-type 'win32-activestate)))
6331              ;; Path must be in Unix format
6332              (let* ((path (if dir
6333                               (w32-cygwin-dos-path-to-cygwin dir)
6334                             "/tmp"))
6335                     (env  (format "PATH=%s" path)))
6336                (push env new)))
6337             (t
6338              (push elt new)))))
6339         ((string-match "^PAGER=" elt)) ;; Delete this
6340         (t
6341          (push elt new))))
6342      (setq process-environment new)
6343      (,@ body))))
6344
6345 ;;; ----------------------------------------------------------------------
6346 ;;;
6347 (defun ti::process-perl-version (&optional binary)
6348   "Check type of perl BINARY.
6349
6350 Return:
6351
6352   (VERSION TYPE PATH OUTPUT)
6353
6354   VERSION   Version number from command line option -version
6355   TYPE      is 'win32-activestate 'win32-cygwin or 'perl
6356   PATH      Path to the BINARY or `perl'.
6357   OUTPUT    Whole output of -v."
6358   (let* ((perl  (if binary
6359                     (executable-find binary)
6360                   (executable-find "perl")))
6361          version
6362          type
6363          string)
6364     (when perl
6365       (with-temp-buffer
6366         (call-process perl
6367                       nil
6368                       (current-buffer)
6369                       nil
6370                       "-v")
6371         (setq string (buffer-string)))
6372       (setq type
6373             (cond
6374              ((string-match "cygwin" string)
6375               'win32-cygwin)
6376              ((string-match "activestate" string)
6377               'win32-activestate)
6378              ((not (ti::nil-p string))
6379               'perl)
6380              (t
6381               (error "Unknown perl type: %s" string))))
6382       ;; This is perl, v5.6.1 built for cygwin-multi
6383       (when (string-match
6384              "This[ \t]+is[ \t]+perl[ ,v\t]+\\([0-9.]+\\)"
6385              string)
6386         (setq version (match-string 1 string)))
6387       (list version type perl string))))
6388
6389 ;;; ----------------------------------------------------------------------
6390 ;;;
6391 (defun ti::process-java-version (&optional binary)
6392   "Return java BINARY type and version number.
6393
6394 Return:
6395
6396   (VERSION TYPE PATH FULL)
6397
6398   VERSION   Version number from command line option -version
6399   TYPE      is 'sun or 'gcc or any other known Java vendor.
6400   PATH      Path to the BINARY or `java'.
6401   FULL      Whole output of -version."
6402
6403   (let* ((java (executable-find (or binary "java")))
6404          version
6405          type
6406          string)
6407     ;;  Under Debian, `call-process' will hang during
6408     ;;  call to /usr/bin/java, which is a symlink
6409     (when (and java
6410                (file-symlink-p java))
6411       (message "TinyLib: %s is symlink, cannot get version." java)
6412       (setq java nil))
6413     (when java
6414       ;; #todo: gcj Java version?
6415       (with-temp-buffer
6416         (call-process java
6417                       nil
6418                       (current-buffer)
6419                       nil
6420                       "-version")
6421         (setq string (buffer-string)))
6422       (when
6423           ;; Java HotSpot(TM) Client VM (build 1.3.0_02, mixed mode)
6424           (or (string-match "build[ \t]+\\([0-9_.]+\\)" string)
6425               ;; Debian:
6426               ;;
6427               ;; java version "1.3.1"
6428               ;; Java(TM) 2 Runtime Environment, Standard Edition \
6429               ;;   (build Blackdown-1.3.1-02b-FCS)
6430               (string-match "java +version +\"\\([0-9][0-9.]+\\)" string))
6431         (setq version (match-string 1 string)))
6432       (cond
6433        ;; Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0_02)
6434        ((string-match "Java(TM)[ \t]+[0-9]" string)
6435         (setq type 'sun))
6436        (t
6437         (setq type 'gcc)))
6438       (list version type java string))))
6439
6440 ;;}}}
6441 ;;{{{ shell: tar
6442
6443 ;;; ----------------------------------------------------------------------
6444 ;;;
6445 (defun ti::process-tar-view-command (file &optional buffer nice verb test)
6446   "Insert tar file listing to point.
6447
6448 Input:
6449
6450   FILE      tar file
6451   BUFFER    default to current buffer
6452   NICE      if non-nil, insert file name and empty lines around listing.
6453   VERB      verbose mode
6454   TEST      Do not execute command. Print what would happen.
6455
6456 Return:
6457
6458   nil       no action [file not exist ...]
6459   nbr       shell return code"
6460   (interactive "fTar file: ")
6461   (let* ((def  (cond
6462                 ((string-match "\\.tar$" file)
6463                  "tar tvf %s")
6464                 ((string-match "\\.tar\\.gz$" file)
6465                  "gzip -d -c %s |tar -tvf -")
6466                 ;;  don't know this currently ...
6467                 ((string-match "\\.tgz$" file)
6468                  nil)))
6469          cmd)
6470
6471     ;; Default tar switches:
6472     ;; -t       ,List the name
6473     ;; -v       ,verbose
6474     ;; -f       ,next arg argument as the name of the archive (file)
6475     ;;
6476     (ti::verb)
6477     (when (and
6478            (stringp file)
6479            (file-exists-p file)
6480            (progn
6481              (or (file-exists-p "/hp-ux/")
6482                  (file-exists-p "/vol/")
6483                  (and verb
6484                       (y-or-n-p
6485                        (format "\
6486 Can't guess tar command, try using default %s ? " def))))
6487              (setq cmd def)))
6488       (if nice
6489           (insert "file " (file-name-nondirectory file) ":\n"))
6490       (call-process cmd nil (or buffer (current-buffer)) nil
6491                     (expand-file-name file))
6492       (if nice (insert "\n")))))
6493
6494 ;;; ----------------------------------------------------------------------
6495 ;;;
6496 (defun ti::process-tar-read-listing-forward  ()
6497   "Read all tar filenames from current line forward.
6498 The point is not preserved. The tar listing looks like:
6499
6500 r-xr-xr-x 240/222   4269 Feb  3 09:25 1997 aa.cc
6501 r-xr-xr-x 240/222  41515 Feb  3 09:40 1997 bb.cc
6502 r-xr-xr-x 240/222   3013 Feb  3 09:40 1997 dd.cc
6503
6504 or
6505
6506 -r--r--r-- foo/bar 14764 1998-06-22 15:05:55 file.txt
6507
6508 Return:
6509
6510  '((FILE SIZE PERMISSIONS) ..)"
6511   (let* ((re (concat
6512               "^\\([drwx-]+\\)[ \t]+[0-9A-Za-z_]+/[0-9A-Za-z_]+"
6513               "[ \t]+\\([0-9]+\\)[ \t]+.*[0-9]:[0-9]+[ \t]+"
6514               "\\(.*\\)"))
6515          list)
6516     (beginning-of-line)
6517     (when (or (looking-at re)
6518               (re-search-forward re nil t))
6519       (beginning-of-line)
6520       (while (and (looking-at re)
6521                   (not (eobp)))
6522         (push (list (match-string 3) (match-string 2) (match-string 1)) list)
6523         (forward-line 1) ))
6524     (nreverse list)))
6525
6526 ;;}}}
6527 ;;{{{ Reading lines, passwords
6528
6529 ;;; ----------------------------------------------------------------------
6530 ;;;
6531 (defun ti::query-read-input-invisible ()
6532   "Read keyboard input. If user presses ESC, the asking is interrupted.
6533 Return:
6534   nil
6535   string"
6536   (let* ((echo-keystrokes 0)            ;prevent showing
6537          str
6538          ch)
6539     (while (not (ti::char-in-list-case ch '(?\n ?\C-m ?\e)))
6540       (cond
6541        ((ti::char-in-list-case ch '(?\b ?\177))
6542         (if (eq 0 (length str))
6543             (beep)
6544           (setq str (substring str 0 (1- (length str)))) ))
6545        ((ti::print-p ch)
6546         (setq str (concat str (char-to-string ch))) ))
6547       (setq ch (ti::read-char-safe-until)))
6548     (if (char= ch ?\e)
6549         (setq str nil))
6550     str))
6551
6552 ;;; ----------------------------------------------------------------------
6553 ;;;
6554 (defun ti::query-read-input-as-password (&optional prompt max echo-char)
6555   "Return read password using PROMPT, MAX chacters with ECHO-CHAR.
6556 If user presses ESC, return nil."
6557   (let* (
6558          (prompt                 (or prompt ""))
6559          (cursor-in-echo-area    nil)
6560          (max                    (or max 80)) ;maximum string
6561          (bar (if echo-char
6562                   (make-string (+ max 2) echo-char )
6563                 (make-string (+ max 2) ?* )))
6564          str
6565          ch
6566          len)
6567     (message prompt)
6568     (while (not (ti::char-in-list-case ch '(?\n ?\C-m ?\e)))
6569       (cond
6570        ((or (ti::char-in-list-case ch '(?\b ?\177)))
6571         (setq len (length str))
6572         (unless (= len 0 )
6573           (setq str (substring str 0 (1- len)))) )
6574        ((ti::print-p ch)
6575         (if (>= (length str) max)
6576             (beep)                      ;signal error
6577           (setq str (concat str (char-to-string ch)))
6578           (message (substring bar 0 (length str)))) ))
6579       (setq ch (ti::read-char-safe-until
6580                 (concat prompt (substring bar 0 (length str))))))
6581     (message "")
6582     (if (char= ch ?\e)
6583         (setq str nil))
6584     str))
6585
6586 ;;}}}
6587
6588 ;;{{{ misc: advice control
6589
6590 ;;; ----------------------------------------------------------------------
6591 ;;;
6592 (defun ti::advice-control
6593   (single-or-list regexp &optional disable verb msg)
6594   "Enables/disable SINGLE-OR-LIST of adviced functions that match REGEXP.
6595 Signals no errors, even if function in LIST is not adviced.
6596 All advice classes ['any] are ena/disabled for REGEXP.
6597
6598 Input:
6599
6600   SINGLE-OR-LIST        function of list of functions.
6601   REGEXP                advice name regexp. Should normally have ^ anchor
6602   DISABLE               flag, if non-nil then disable
6603   VERB                  enable verbose messages
6604   MSG                   display this message + on/off indication"
6605   (dolist (func (ti::list-make single-or-list))
6606     (ignore-errors
6607       (if disable
6608           (ad-disable-advice  func 'any regexp)
6609         (ad-enable-advice     func 'any regexp))
6610       ;;change state
6611       (ad-activate func)))
6612   (if verb
6613       (message
6614        (concat
6615         (or msg "advice(s): ")
6616         (if disable "off" "on")))))
6617
6618 ;;}}}
6619
6620 ;;{{{ misc: -- packaging, install, reports
6621
6622 ;;; ..................................................... &bug-reports ...
6623 ;;; - Take a look at lisp-mnt.el if you're writing
6624 ;;;   your own packages.
6625
6626 ;;; ----------------------------------------------------------------------
6627 ;;; #defalias (defalias 'package-feedback 'ti::package-feedback)
6628 ;;;
6629 (defun ti::package-submit-feedback (lib)
6630   "Composes feedback report with lisp-mnt.el conmoncerning Lisp file LIB.
6631 Make sure the file beeing reported is valid according to
6632 lisp-mnt's command `lm-verify'."
6633   (interactive "sSend mail regarding file: ")
6634   (let (file
6635         version
6636         buffer)
6637     (cond
6638      ((setq file
6639             (or (locate-library lib)
6640                 (progn
6641                   (setq lib (concat lib ".gz"))
6642                   (locate-library lib))))
6643       (require 'lisp-mnt)
6644       (set-buffer (setq buffer (ti::find-file-literally file)))
6645       (setq version (ti::vc-rcs-buffer-version))
6646       (lm-report-bug
6647        (format "%s %s Feedback"
6648                (or version "")
6649                (file-name-nondirectory file)))
6650       (kill-buffer buffer))
6651      (t
6652       (error (concat "No such file in load path: " lib))))))
6653
6654 ;;; ----------------------------------------------------------------------
6655 ;;; - See package tinydiff.el and function tdi-feedback there if you
6656 ;;;   are still curious how to use this function
6657 ;;;
6658 (defun ti::package-submit-bug-report
6659   (lib id var-list &optional verb elts)
6660   "Submit bug report with reporter.
6661
6662 PRECONDITIONS before using this function
6663
6664 1. The file must be in version control and it must have the \"\$ Id \$\" identifier
6665    stored into variable. Like the following:
6666
6667    (defconst tinylib-version-id
6668      \"\$ Id: tinylib.el,v 1.18 1996/01/24 09:44:48 jaalto Exp jaalto \$\"
6669      \"Latest modification time and version number.\")
6670
6671 2. The package must be valid according to lisp-mnt.el's command
6672    `lm-verify' so that the \"maintainer\" information can be extracted.
6673    This means that you file must have header like this:
6674
6675   ;; Maintainer:   Foo Bar <foo@example.com>
6676
6677 Input:
6678
6679   LIB           filename without path. E.g. \"tinylib.el\"
6680   ID            the RCS Id string
6681   VAR-LIST      list of variables to get from package. Like '(var1 var2)
6682   VERB          Verbose messages and questions.
6683   ELTS          a) Buffer to included in report.
6684                 b) If this is functionp, then function must return a
6685                    string or buffer pointer to include.
6686                 c) if this is boundp, the value is taken as buffer
6687                    name string."
6688   (interactive)
6689   (let* (maintainer
6690          subj
6691          list)
6692     (ti::verb)
6693     (require 'reporter)
6694     (setq maintainer
6695           (or (car-safe (ti::package-get-header lib  "maintainer")) ""))
6696     (setq list (split-string id " "))
6697     (setq subj (concat (nth 2 list) " " (nth 1 list))) ;; name && version
6698     ;; ................................................... compose mail ...
6699     (when (or (null verb)
6700               (y-or-n-p "Do you really want to submit a report? "))
6701       (reporter-submit-bug-report
6702        maintainer
6703        (nth 1 list)
6704        var-list
6705        nil nil
6706        (concat "Hi,\n"))
6707       ;; ............................................... insert content ...
6708       (let (status
6709             str
6710             name
6711             len
6712             function)
6713         (dolist (buffer elts)
6714           (setq str      nil
6715                 status   nil
6716                 function nil)
6717           ;; .............................................. detect type ...
6718           (cond
6719            ((stringp buffer)
6720             (setq status (get-buffer buffer)))
6721            ((memq buffer '(nil t))) ;; Ignore
6722            ((and (symbolp buffer)
6723                  (boundp buffer))
6724             (setq buffer (symbol-value buffer))
6725             (if (stringp buffer)
6726                 (setq status (get-buffer buffer))
6727               (message "TinyLib: bug report ERROR. Malformed syntax %s"
6728                        (prin1-to-string buffer))
6729               (sleep-for 3)))
6730            ((functionp buffer)
6731             (setq function buffer)
6732             (setq status (funcall function))
6733             (cond
6734              ((stringp status)
6735               (setq str status))
6736              ((bufferp status)
6737               (setq buffer status)
6738               (setq status t)))))
6739           (when buffer
6740             (when (and (interactive-p)
6741                        (null status))
6742               (or
6743                (y-or-n-p (format "Buffer `%s' missing, continue? Are you sure? "
6744                                  (prin1-to-string buffer)))
6745                (error "Abort.")))
6746             ;; ................................................. insert ...
6747             (when status
6748               (setq name (cond
6749                           ((bufferp buffer)
6750                            (buffer-name buffer))
6751                           ((stringp buffer)
6752                            buffer)
6753                           (t
6754                            (symbol-name function))))
6755               (setq len (- 70 (length name)))
6756               (insert "\n\n[" name "] " (make-string len ?= ) "\n\n")
6757               (setq len (buffer-size))
6758               (if str
6759                   (insert str)
6760                 (insert-buffer buffer))
6761               ;;  `insert-buffer' does not put point after insert,
6762               ;;  go there manually
6763               (when (> (buffer-size) len)
6764                 (forward-char (- (buffer-size) len)))))))
6765       ;; ............................................... position point ...
6766       (ti::pmin)
6767       (if (re-search-forward "Subject: *" nil t)
6768           (insert subj))
6769       (re-search-forward "Hi,\n"))))
6770
6771 ;;; ----------------------------------------------------------------------
6772 ;;;
6773 (defun ti::package-version-info (lib &optional arg)
6774   "Gets package information and prints it to another buffer.
6775 The LIB is searched along 'load-path'.
6776
6777 Preconditions:
6778
6779   The file must be valid according to lisp-mnt.el::lm-verify
6780
6781 Interactive call:
6782
6783   You can complete the filename with TAB key
6784
6785 Input:
6786
6787   LIB   filename with .el added
6788   ARG   prefix arg, print the versionin info in mode-line
6789         instead of creating full version buffer."
6790   (interactive
6791    (let* (file)
6792      (setq
6793       file
6794       (ti::file-complete-filename-minibuffer-macro
6795         (read-from-minibuffer
6796          (format "[%s] Version info for library: " default-directory)
6797          nil
6798          map)))
6799      (if (null file)
6800          (setq file file))         ;XEmacs 19.14 bytecompiler silencer
6801      ;; Make sure there is .el
6802      (list
6803       (ti::string-verify-ends file ".el")
6804       current-prefix-arg)))
6805   (let (out
6806         file
6807         buffer
6808         tmp
6809         lm-version
6810         lm-summary
6811         lm-maintainer
6812         lm-creation-date
6813         lm-last-modified-date
6814         lm-commentary
6815         rcs-id
6816         maintainer-name
6817         maintainer-email)
6818     (cond
6819      ((setq file
6820             (or (locate-library lib)
6821                 (progn
6822                   (setq lib (concat lib ".gz"))
6823                   (locate-library lib))))
6824       (require 'lisp-mnt)
6825       (cond
6826        ((not (null arg))
6827         (set-buffer (setq buffer (ti::find-file-literally file)))
6828         (setq rcs-id (or (ti::vc-rcs-str-find-buffer "Id") "<no rcs id>"))
6829         (kill-buffer buffer)
6830         (ti::read-char-safe-until rcs-id))
6831
6832        (t
6833         (setq out (ti::temp-buffer "*version*" 'clear))
6834         ;;  Now get the information from file with lisp-mnt.el
6835         (with-current-buffer (setq buffer (ti::find-file-literally file))
6836           (setq
6837            lm-version             (lm-version)
6838            lm-summary             (lm-summary)
6839            lm-maintainer          (lm-maintainer)
6840            lm-creation-date       (lm-creation-date)
6841            lm-last-modified-date  (lm-last-modified-date)
6842            lm-commentary          (lm-commentary)
6843            rcs-id                 (ti::vc-rcs-str-find-buffer "Id")))
6844         (when (and (stringp lm-last-modified-date)
6845                    (eq 3 (length (setq tmp (split-string lm-last-modified-date))))
6846                    (eq 3 (length (nth 1 tmp))))
6847           ;;  Convert "16 Feb 2000" --> to ISO 8601 Date
6848           (setq lm-last-modified-date
6849                 (format "%s-%s-%s"
6850                         (nth 2 tmp)
6851                         (ti::month-to-0number (nth 1 tmp))
6852                         (nth 0 tmp))))
6853         (kill-buffer buffer)
6854         (setq maintainer-name
6855               (if  (not (null lm-maintainer))
6856                   (or (car-safe lm-maintainer) "<name not known>")
6857                 "<name not known>"))
6858         (setq maintainer-email
6859               (if  (not (null lm-maintainer))
6860                   (or (cdr-safe lm-maintainer) "no email info")
6861                 "no email info"))
6862         (switch-to-buffer-other-window out)
6863         (insert
6864          lib " -- "        (or lm-summary            "<no info>")        "\n\n"
6865          "Created      : " (or lm-creation-date      "<no info>")        "\n"
6866          "Last modified: " (or lm-last-modified-date "<no info>")        "\n"
6867          "Maintainer   : " maintainer-name " <" (or maintainer-email "") ">\n"
6868          "Version      : " (or lm-version            "<no info>")        "\n"
6869          "\n\n"
6870          (or lm-commentary "<no commentary found>"))
6871         (pop-to-buffer  out)
6872         (ti::pmin) (ti::buffer-replace-regexp "^;;;" 0 "   ")
6873         (ti::pmin) (ti::buffer-replace-regexp "^;;"  0 "  ")
6874         (ti::pmin) (ti::buffer-lf-to-crlf 'dos2unix 'force)
6875         (ti::pmin))))
6876      (t
6877       (error (concat "No such file in load path: " lib))))))
6878
6879 ;;; ----------------------------------------------------------------------
6880 ;;;
6881 ;;;
6882 (defun ti::package-get-header (lib header-list)
6883   "Get standard header information: e.g. maintainer, version, author.
6884 The valid syntax of these headers is defined in lisp-mnt.el.
6885 Make sure the file being visited can be run with  lisp-mnt's
6886 command `lm-verify'.
6887
6888 Input:
6889
6890   LIB           the filename of the package, including \".el\"
6891   HEADER-LIST   string or list of strings. E.g. '(\"maintainer\")
6892
6893 Return:
6894
6895   list          notice that empty hits are stored: '(nil nil ..)
6896   nil"
6897   (let ((header-list  (ti::list-make header-list))
6898         hit elt
6899         file
6900         buffer
6901         ret)
6902     (cond
6903      ((setq file (locate-library lib))
6904       (require 'lisp-mnt)
6905       (unwind-protect                   ;make sure file is removed
6906           (progn
6907             (set-buffer (setq buffer (ti::find-file-literally file)))
6908             (mapcar
6909              (function
6910               (lambda (header)
6911                 (setq elt (lm-header header))
6912                 (if elt                         ;did we find any ?
6913                     (setq hit t))               ;raise flag
6914                 (push elt ret)))
6915              header-list))
6916         ;; Kill the file no matter what happens.
6917         (kill-buffer buffer)))
6918      (t
6919       (error (concat "No such file in load path: " lib))))
6920     (if (null hit)                    ;if no hits, clear the ret value
6921         (setq ret nil))
6922     ret))
6923
6924 ;;; ......................................................... &package ...
6925 ;;; - Here is some special functions. When you insert some example to
6926 ;;;   your package, you can convert functions and text directly to
6927 ;;;   "poor man's shar" format :-)
6928 ;;; - With function ti::package-make-mode-magic, you just
6929 ;;;
6930 ;;;   1. Be in lisp mode
6931 ;;;   2. Select example area to be inserted into somewhere
6932 ;;;   3. call the functions --> The result is inserted into registed
6933 ;;;   4. Go to package buffer and insert the register contents there.
6934 ;;;
6935 ;;; - Likewise the user can rip these "shar" examples with function
6936 ;;;   ti::package-rip-magic
6937 ;;;
6938 ;;;   1. Select area and call the function. --> examples in register
6939 ;;;   2. Put them into your .emacs or another favourite file.
6940 ;;;
6941 ;;; - Use similar bindings
6942 ;;;   (global-set-key   "\C-cp" 'ti::package-make-mode-magic)
6943 ;;;   (global-set-key   "\C-cP" 'ti::package-rip-magic)
6944
6945 ;;; ----------------------------------------------------------------------
6946 ;;
6947 (defun ti::package-install-example (lib &optional re)
6948   "Install example setup for you from LIB.
6949 The LIB must be normal source file name ending in '.el'.
6950 Function tries to find $PackageInstallRe: 'REGEXP' $
6951 line which has the installation code chars in the surrounding
6952 quotes. The common practise is to have '^[ \t]*;;+[*]' for Lisp.
6953 If that regexp is followed by char '_' it means that the line is left empty.
6954
6955 If you supply RE, it must have match in LEVEL 1.
6956
6957 Return:
6958   buffer pointer"
6959   (interactive "sLibrary: ")
6960   (let* ((tmp  "*ti::pkg*")
6961          (file (locate-library lib))
6962          (verb (interactive-p))
6963          ;;    There has to be " " after the ":" otherwise it's not
6964          ;;    rcs ident(1) compatible. Also before the last $ ,
6965          ;;    there must be space.
6966          (re   (or re "[$]PackageInstallRe: [ \t]*'\\(.*\\)' [$]"))
6967          (empty-line-ch   "_")
6968          bp                             ;buffer pointer
6969          id
6970          comment-re)
6971     (if (or (null file)
6972             (null (file-readable-p file)))
6973         (error (concat "Cannot locate/read " lib " in load-path: " file))
6974       (setq bp (ti::temp-buffer tmp 'clear))
6975       (with-current-buffer bp
6976         (insert-file-contents file)
6977         (ti::pmin)
6978         (if (or (null (re-search-forward re nil t))
6979                 (null (match-end 1)))
6980             (progn
6981               (pop-to-buffer bp)
6982               (error (concat "Cannot find install regexp: " re)))
6983           (setq comment-re (match-string 1)) ;read match in level 1
6984           (if (ti::nil-p comment-re)
6985               (error (concat "Level 1 mismatch_" (match-string 0) "_" re)))
6986           (save-excursion (setq id (ti::vc-rcs-str-find "Id" )))
6987           (ti::package-rip comment-re empty-line-ch (point-min) (point-max) )
6988           (ti::pmin)
6989           ;;  And final touch, add version id if it existed.
6990           (if (null id )
6991               (insert (concat ";; No rcs id found.\n\n"))
6992             (insert (concat ";; " id "\n\n")))
6993           ;;  Show contents if user called interactively.
6994           (when verb
6995             (pop-to-buffer bp)
6996             (message "Automatic install done.")))))
6997     bp))
6998
6999 ;;; ----------------------------------------------------------------------
7000 ;;
7001 (defun ti::package-rip (re ch &optional beg end)
7002   "Delete section of commented text, so that only code remains.
7003 The installed code portion should have RE at front of each line.
7004
7005 RE must have anchor ^ and CH must have some magic char to
7006 mean empty line. like RE = '^;;+[*]' and CH = '_':
7007
7008     ;;*  ;;This belongs to automatic install, below is empty line code
7009     ;;*  _
7010
7011 Input:
7012
7013  RE            ,regexp matching the examples
7014  CH             character signifying empty lines
7015  BEG END        area bounds
7016
7017 Return:
7018
7019   t or nil"
7020   (interactive)
7021   (let* (ret)
7022     (unless (and beg end)
7023       (pop-to-buffer (current-buffer))
7024       (error "ti::package-rip: Region not defined %s" (current-buffer)))
7025     (save-restriction
7026       (narrow-to-region beg end)
7027       (ti::pmin)
7028       (when (re-search-forward re nil t)
7029         (ti::pmin)
7030         (save-excursion (delete-non-matching-lines re))
7031         ;; Now we have only RE lines
7032         (while (not (eobp))
7033           (when (looking-at re)
7034             (delete-region (match-beginning 0) (match-end 0))
7035             (if (looking-at ch)         ;remove that char
7036                 (delete-char 1)))
7037           (forward-line)
7038           (setq ret t))))
7039     ret))
7040
7041 ;;; ----------------------------------------------------------------------
7042 ;;;
7043 (defun ti::package-rip-magic (beg end &optional verb)
7044   "As `ti::package-rip' BEG END, except the area is pasted to temporary buffer.
7045 Tthe lines are prepared AND the result is inserted to register. VERB.
7046
7047 Make sure your are viewing the piece of code in the same mode that it is
7048 supposed to be used. Otherwise the magic syntax isn't regognized.
7049
7050 Return:
7051   t or nil"
7052   (interactive "r")
7053   (let* ((ob      (current-buffer))
7054          (str     (ti::package-make-var))
7055          (empty   "_")
7056          (reg     ?p)                   ; "p" as "package"
7057          ret
7058          re)
7059     (ti::verb)
7060     (if (ti::nil-p str)
7061         (error "\
7062 Couldn't set rip syntax, maybe `comment-start' is not defined.")
7063       (with-temp-buffer
7064         (insert-buffer-substring ob beg end) ;get the area
7065         (setq re (concat "^" (regexp-quote str)))
7066         (setq ret (ti::package-rip re empty (point-min) (point-max)))
7067         (pop-to-buffer (current-buffer))
7068         (cond
7069          (ret
7070           (set-register reg (buffer-string))
7071           (if verb
7072               (message "Example ripped to register `%c' " reg)))
7073          (t
7074           (when verb
7075             (message "could find Rip regexp `%s' from region." re))))))
7076     ret))
7077
7078 ;;; ----------------------------------------------------------------------
7079 ;;;
7080 (defun ti::package-make-mode-magic (beg end)
7081   "As `ti::package-make-mode', except BEG END is pasted to temporary buffer.
7082 The lines are prepared AND the result is inserted to register.
7083
7084 Return:
7085   t or nil according to success."
7086   (interactive "r")
7087   (let* ((source (current-buffer))       ;source buf
7088          (m      major-mode)             ;we must use same mode
7089          (verb   (interactive-p))
7090          (reg    ?p))
7091     (with-temp-buffer
7092       (insert-buffer-substring source beg end)
7093       ;;  turning mode on may have effects, since it runs hooks...
7094       ;;
7095       (funcall m)                       ;turn on same mode
7096       (when (ti::package-make-mode (point-min) (point-max))
7097         (set-register reg (buffer-string))
7098         (if verb
7099             (message "example in register `%c'" reg))))))
7100
7101 ;;; ----------------------------------------------------------------------
7102 ;;;
7103 ;;;
7104 (defun ti::package-make-mode (beg end)
7105   "Make embedded package around BEG END according to mode.
7106 ** DOES NOT WORK FOR MODES WITH `comment-end' ***
7107
7108 Return:
7109   nil or t if successfull."
7110   (interactive "*r")
7111   (let* ((str     (ti::package-make-var))
7112          (empty   "_")
7113          ret)
7114     (if (not (ti::nil-p comment-end))
7115         (message "tinylib: Comment end found, cannot proceed.")
7116       (ti::package-make beg end str empty)
7117       (setq ret t))
7118     ret))
7119
7120 ;;; ----------------------------------------------------------------------
7121 ;;;
7122 (defun ti::package-make-var ()
7123   "Return Packaging variable 'str' according to mode.
7124 If mode has no comment syntax default ';;* ' is used."
7125   (let* ((cs comment-start)
7126          (cs (cond                      ;set up something special
7127               ((memq major-mode
7128                      '(lisp-mode emacs-lisp-mode lisp-interaction-mode))
7129                (setq cs ";;"))          ;default ';' isn't enough
7130               (t cs)))                  ;do not change it
7131          (str (if (null cs)
7132                   ";;* "
7133                 ;; make sure there is space
7134                 (concat cs "* "))))
7135     str))
7136
7137 ;;; ----------------------------------------------------------------------
7138 ;;;
7139 (defun ti::package-make (beg end str ch)
7140   "Format area for automatic install.
7141
7142 Input:
7143
7144   BEG END       area
7145   STR           string to be added at front
7146   CH            additional character for empty lines."
7147   (let* ((empty (concat str
7148                         (cond
7149                          ((integerp ch)
7150                           (char-to-string ch))
7151                          (
7152                           ch)))))
7153     (save-restriction
7154       (narrow-to-region beg end)
7155       (goto-char (min beg end))
7156       (while (not (eobp))
7157         (if (looking-at "^[ \t]*$")
7158             (insert empty)
7159           (insert str))
7160         (forward-line 1)))))
7161
7162 ;;; ----------------------------------------------------------------------
7163 ;;;
7164 (defun ti::package-autoload-create-on-file
7165   (file &optional buffer no-show no-desc)
7166   "Very simple autoload function generator out of FILE.
7167 Optionally put results to BUFFER. NO-SHOW does not show buffer.
7168
7169 Note:
7170
7171   Doesn't recognize ###autoload tags; reads only functions.
7172
7173 Input:
7174
7175   FILE      Lisp .el to read
7176   BUFFER    Where to insert autoloads.
7177   NO-SHOW   Do not show autoload buffer
7178   NO-DESC   Do not include function description comments."
7179   (interactive "fConstruct lisp autoloads from file: ")
7180   (let* ((fn     (file-name-nondirectory file))
7181          (regexp (concat
7182                   "^(\\("
7183                   "defun\\|defmacro\\|defsubst"
7184                   ;; SEMI poe.el
7185                   "\\|defun-maybe\\|defsubst-maybe\\|defmacro-maybe"
7186
7187                   "\\)"
7188                   "[ \t]+\\([^ \t\n(]+\\)[ \t]*"))
7189          list
7190          args
7191          func
7192          type
7193          str
7194          iact
7195          point
7196          read-buffer
7197          tmp)
7198     (or buffer
7199         (setq buffer (get-buffer-create (or buffer  "*Autoloads*"))))
7200     ;;   We want to say (autoload 'func "pacakge" t t)
7201     ;;   and not        (autoload 'func "pacakge.el" t t)
7202     ;;   so that .elc files can be used.
7203     (if (string-match "\\(.*\\).el" fn)
7204         (setq fn (match-string 1 fn)))
7205     (unless (setq read-buffer (find-buffer-visiting file))
7206       (setq read-buffer (setq tmp (ti::find-file-literally file))))
7207     (with-current-buffer read-buffer
7208       ;; Can't use forward-sexp etc otherwise
7209       (unless (string-match "lisp" (symbol-name major-mode))
7210         (let (emacs-lisp-mode-hook) ;; Run no hooks
7211           (if emacs-lisp-mode-hook  ;; Quiet ByteCompiler "unused var"
7212               (setq emacs-lisp-mode-hook nil))
7213           (emacs-lisp-mode)))
7214       (ti::append-to-buffer
7215        buffer  (concat "\n;; "
7216                        (file-name-nondirectory file)
7217                        "\n"
7218                        ";; "
7219                        file
7220                        "\n\n"))
7221       (ti::pmin)
7222       (while (re-search-forward regexp nil t)
7223         (setq iact nil                  ;interactive flag
7224               args nil
7225               type (match-string 1)
7226               func (match-string 2))
7227         (when (and func
7228                    (progn
7229                      (goto-char (goto-char (match-end 0)))
7230                      (when (search-forward "(" nil t)
7231                        (setq point (point))
7232                        (backward-char 1)
7233                        (forward-sexp 1)
7234                        (backward-char 1)
7235                        (setq
7236                         args
7237                         (subst-char-in-string
7238                          ;;  Convert multiline args to one line.
7239                          ?\n ?\
7240                          (buffer-substring point (point)) )))))
7241         (if (re-search-forward
7242              "[ \t\n]+([ \t]*interactive"
7243              (save-excursion (end-of-defun) (point))
7244              t)
7245             (setq iact "t"))
7246         (cond
7247          ((null args)
7248           (setq args (format ";; %-36s <args not known>\n" func))
7249           ((string= args "")
7250            (setq args (format ";; %s\n" func)))
7251           ((> (length args) 32)
7252            (setq args (format ";; %-15s %s\n" func args)))
7253           (t
7254            (setq args (format ";; %-36s %s\n" func args)))))
7255         (push args list)
7256         ;; (autoload FUNCTION FILE &optional DOCSTRING INTERACTIVE TYPE)
7257         (setq str (format "(autoload '%-36s %s \"\" %s%s)%s\n"
7258                           func
7259                           (format "\"%s\"" fn)
7260                           (or iact "nil")
7261                           (if (string-match "defmacro" type )
7262                               " 'macro" "")
7263                           (if (string= type "defsubst")
7264                               (format ";;%s" type) "")))
7265         (ti::append-to-buffer buffer str)
7266         (setq iact "t")))
7267     (unless no-desc
7268       (with-current-buffer buffer
7269         (insert "\n")                   ;list arguments for functions.
7270         (dolist (elt list) (insert elt)))))
7271   (if tmp                          ;We loaded this to Emacs, remove it
7272       (kill-buffer tmp))
7273   (unless no-show
7274     (pop-to-buffer buffer)
7275     (ti::pmin))
7276   buffer))
7277
7278 ;;; ----------------------------------------------------------------------
7279 ;;;
7280 (defun ti::package-autoload-create-on-directory
7281   (dir &optional buffer no-show no-desc)
7282   "Create autoloads from function definitions in lisp files in DIR.
7283 Optionally put results to BUFFER. NO-SHOW does not show buffer.
7284
7285 Note:
7286
7287   Doesn't recognize ###autoload tags; reads only functions.
7288
7289 Input:
7290
7291   See argument description in function `ti::package-autoload-create-on-file'."
7292   (let* ((files (directory-files
7293                  dir
7294                  'full
7295                  "\\.el$")))
7296     (dolist (file files)
7297       (ti::package-autoload-create-on-file file buffer no-show no-desc))))
7298
7299 ;;; ----------------------------------------------------------------------
7300 ;;;
7301 (defun ti::package-autoload-loaddefs-create-maybe (file)
7302   "Make sure `generated-autoload-file' exists for FILE."
7303   (unless (file-exists-p file)
7304     (let* ((name1 (file-name-nondirectory file)))
7305       (with-temp-buffer
7306         (insert
7307          (format ";;; %s -- " name1)
7308          "loaddef definitions of program files\n"
7309          ";;  Generate date: " (format-time-string "%Y-%m-%d" (current-time))
7310          "\n\
7311 ;;  This file is automatically generated. Do not Change."
7312          "\n\n"
7313          (format "\n(provide '%s)\n\n"
7314                  (file-name-sans-extension (file-name-nondirectory name1))))
7315         (ti::with-coding-system-raw-text
7316           (write-region (point-min) (point-max) file))))))
7317
7318 ;;; ----------------------------------------------------------------------
7319 ;;;
7320 (defun ti::package-autoload-loaddefs-dir-files (dir &optional regexp)
7321   "Return from DIR .el files that do not matching REGEXP.
7322 TO-FILE is excluded from autoload search."
7323   (let* (ret)
7324     (dolist (file (directory-files dir 'abs))
7325       (when (and (not (file-directory-p file))
7326                  (string-match "\.el$" file)
7327                  (or  (null regexp)
7328                       (not (string-match regexp file))))
7329         (push file ret )))
7330     ret))
7331
7332 ;;; ----------------------------------------------------------------------
7333 ;;;
7334 (defun ti::package-autoload-loaddefs-build-dir-1 (dir &optional regexp to-file)
7335   "Build autoloads in DIR not matching REGEXP TO-FILE."
7336   (let ((files (ti::package-autoload-loaddefs-dir-files dir regexp)))
7337     (when files
7338       (let* (
7339              ;;  the original Emacs autload.el var does not contain "^"
7340              ;;  and this picks up wrong autoload definitions e.g. in
7341              ;;  auctex/tex-info.el which contains code
7342              ;;  ;;; Do not ;;;###autoload because conflicts standard texinfo.el.
7343              ;;  (defun texinfo-mode ()
7344              ;;
7345              ;; (generate-autoload-cookie "^;;;###autoload")
7346              ;;
7347              ;;  ...but, we cannot do that because
7348              ;;  generate-autoload-cookie is not a regexp, because in
7349              ;;  autoload.el there is statement in
7350              ;;  generate-file-autoloads()
7351              ;;
7352              ;;      (regexp-quote generate-autoload-cookie)
7353              ;;
7354              find-file-hooks
7355              write-file-hooks
7356              font-lock-mode
7357              ;; buffer-auto-save-file-name
7358              auto-save-hook
7359              auto-save-default
7360              (auto-save-interval 0)
7361              (original-backup-inhibited backup-inhibited)
7362              (backup-inhibited t))
7363         ;; Reset also global
7364         (setq-default backup-inhibited t)
7365         ;;  When each file is loaded to emacs, do not turn on lisp-mode
7366         ;;  or anything else => cleared file hooks. These are byte compiler
7367         ;;  silencers:
7368         (if (null find-file-hooks)
7369             (setq find-file-hooks nil))
7370         (if (null write-file-hooks)
7371             (setq write-file-hooks nil))
7372         (if (null font-lock-mode)
7373             (setq font-lock-mode nil))
7374         (if (null auto-save-hook)
7375             (setq auto-save-hook nil))
7376         (if (null auto-save-default)
7377             (setq auto-save-default nil))
7378         (if auto-save-interval
7379             (setq auto-save-interval 0))
7380         (if backup-inhibited
7381             (setq backup-inhibited t))
7382         (ti::package-autoload-loaddefs-create-maybe to-file)
7383         (dolist (file files)
7384           ;; (message "TinyLib: Updating loaddefs %s %s"
7385           ;; generated-autoload-file file)
7386           (message "TinyLib: Updated loaddefs %s => %s" dir to-file)
7387           (update-file-autoloads file))
7388         (setq-default backup-inhibited original-backup-inhibited)))))
7389
7390 ;;; ----------------------------------------------------------------------
7391 ;;;
7392 (defun ti::package-autoload-loaddefs-build-dir
7393   (dir to-file &optional regexp force)
7394   "Build autoloads in DIR TO-FILE like like `update-file-autoloads' does.
7395
7396 Input:
7397
7398   DIR       Directory
7399   TO-FILE   The autoload file
7400   REGEXP    Ignore files matching regexp.
7401   FORCE     If non-nil, delete previous TO-FILE."
7402   (let* ((generated-autoload-file to-file) ;; See autoload.el, must be bound
7403          (name          (file-name-nondirectory to-file))
7404          (buffer        (find-buffer-visiting to-file))
7405          load)
7406     (unless generated-autoload-file ;; just byte compiler silencer.
7407       (setq generated-autoload-file nil))
7408     ;;  Exclude to-file from search.
7409     (if regexp
7410         (setq regexp (concat regexp "\\|" (regexp-quote name)))
7411       (setq regexp (regexp-quote name)))
7412     (when buffer
7413       (ti::kill-buffer-safe buffer)
7414       (setq load t))
7415     (when (and force
7416                (file-exists-p to-file))
7417       (ti::file-delete-safe to-file))
7418 ;;;    (dolist (file (ti::package-autoload-loaddefs-dir-files dir regexp))
7419 ;;;      (message "TinyLib: loaddefs %s %s" generated-autoload-file file)
7420 ;;;      (update-file-autoloads file))
7421     (ti::package-autoload-loaddefs-build-dir-1 dir regexp to-file)
7422     (when (setq buffer (find-buffer-visiting to-file))
7423       (with-current-buffer buffer
7424         (let (buffer-auto-save-file-name
7425               auto-save-default)
7426           (save-buffer))))
7427     (when load ;;  Reload, because buffer was in Emacs
7428       (find-file-noselect to-file))))
7429
7430 ;;; ----------------------------------------------------------------------
7431 ;;;
7432 (defun ti::package-autoload-directories (list)
7433   "Return only directories from LIST, excluding version control directories."
7434   (let* (ret)
7435     (dolist (elt list)
7436       (when (and (file-directory-p elt)
7437                  ;;  Drop . ..
7438                  (not (string-match
7439                        "[/\\]\\..?$\\|CVS\\|RCS"
7440                        elt)))
7441         (push elt ret)))
7442     ret))
7443
7444 ;;; ----------------------------------------------------------------------
7445 ;;;
7446 (defun ti::package-autoload-loaddefs-build-recursive
7447   (dir regexp &optional force function)
7448   "Build like `update-file-autoloads' recursively below DIR.
7449 Input:
7450
7451   DIR       Root directory to start searching
7452   REGEXP    Regexp to exclude files.
7453   FORCE     Recreate TO-FILE from scratch by deleting previous.
7454             You should do this if you have renamed any files in the directories.
7455   FUNCTION  Function to return autoload filename for each directory.
7456             Called with arg `dir'. The default file is loaddefs.el."
7457   (interactive "DEmacs autoload build root:\nfTo file: ")
7458   (unless dir
7459     (error "need DIR"))
7460   (let* ((dirs (ti::package-autoload-directories
7461                 (directory-files
7462                  (expand-file-name dir)
7463                  'abs)))
7464          (to-file (or (and function
7465                            (funcall function dir))
7466                       "loaddefs.el")))
7467     (cond
7468      (dirs
7469       (ti::package-autoload-loaddefs-build-dir dir to-file regexp force)
7470       (dolist (dir dirs)
7471         (ti::package-autoload-loaddefs-build-recursive
7472          dir regexp force function)))
7473      (t
7474       (ti::package-autoload-loaddefs-build-dir dir to-file regexp force)))))
7475
7476 ;;; ----------------------------------------------------------------------
7477 ;;;
7478 (defun ti::package-install-pgp-tar  (dir &optional log-buffer source test)
7479   "Install PGP signed tar block using DIR from the end of current buffer.
7480 The 'BEGIN PGP MESSAGE' is searched from the end of buffer backward.
7481
7482 The TAR block in the buffer looks like this and it is base64 pgp
7483 signed (clearsig is off) with Author's public key.
7484
7485     ;; -----BEGIN PGP MESSAGE-----
7486     ;; Version: 2.6.3ia
7487     ;;
7488     ;; owHsWc1vG0l2n0GwwYjA3pJLgEXKlNaSDJLilySblrWWLXrMrCQrpOydzcxA02wW
7489     ;; ...
7490     ;; ...
7491     ;; -----END PGP MESSAGE-----
7492
7493 This function
7494
7495 o   Asks to what directory the tar files are installed.
7496 o   shows the log buffer and echoes commads used.
7497 o   Calls pgp to unpack the signed block
7498 o   Calls tar to unpack the files
7499 o   temporary files are stored to TMP, TMPDIR or /tmp
7500
7501 Error conditions:
7502
7503 o   if 'pgp' executable is not found, function aborts.
7504 o   if 'tar' executable is not found, function aborts.
7505 o   if previously installed files exists, function aborts.
7506
7507 Input:
7508
7509   DIR           where to unpack the files
7510   LOG-BUFFER    where to print log messages.
7511   SOURCE        instead of using current buffer, read this file"
7512
7513   (interactive "DSave programs to directory: ")
7514   (let* (
7515          (pgp     (or (and (executable-find "pgp")
7516                            ;;  Do not use returned absolute path
7517                            ;;  due to platform independency
7518                            "pgp")
7519                       (message "TinyLib: Can't find `pgp'.")))
7520          (gpg     (or (and (executable-find "pgp")
7521                            "pgp")
7522                       (message "TinyLib: Can't find `gpg'.")))
7523          (pgp-bin (or pgp gpg))
7524          (tar     (or (executable-find "tar")
7525                       (error "TinyLib: Can't find 'tar'.")))
7526          (tmp     (or (and (getenv "TMP")
7527                            (ti::file-make-path (getenv "TMP")))
7528                       (and  (getenv "TMPDIR")
7529                             (ti::file-make-path (getenv "TMPDIR")))
7530                       "/tmp/"))
7531          ;;  This may be system dependent someday..
7532          (tar-opt-show "tvf")
7533          (tar-opt-x    "xvf")
7534          (obuffer  (current-buffer))
7535          (in-file  (expand-file-name (concat tmp "t.in")))
7536          (out-file (expand-file-name (concat tmp "t.out")))
7537          cmd
7538          in
7539          buffer
7540          beg
7541          end
7542          file-list
7543          list)
7544     (unless pgp-bin
7545       (error "TinyLib: PGP or GPG is required to unpack."))
7546     ;; We need to expand this for shell calls
7547     (setq dir (expand-file-name (ti::file-make-path dir)))
7548     (cond
7549      ((and source
7550            (not (file-exists-p source)))
7551       (error "TinyLib: Can't find '%s'" source))
7552      ((not (file-directory-p tmp))
7553       (error "TinyLib: Can't use directory '%s'. Set env variable TMP." tmp))
7554      ((not (file-exists-p dir))
7555       (error "TinyLib: No such directory %s." dir)))
7556     (setq buffer (ti::temp-buffer
7557                   (or log-buffer "*tinylib::install*")
7558                   'clear))
7559     (with-current-buffer buffer
7560       ;; .............................................. extract base64 ...
7561       (buffer-disable-undo)
7562       (if source
7563           (insert-file-contents source)
7564         (insert-buffer obuffer))
7565       (ti::pmax)
7566       (unless (re-search-backward
7567                (concat "^;;+[ \t]*\\(" (ti::mail-pgp-msg-end-line) "\\)")
7568                nil t)
7569         (pop-to-buffer (current-buffer))
7570         (error "TinyLib: Can't find PGP end %s " source))
7571       (setq end (match-beginning 1))
7572       (unless (re-search-backward
7573                (concat "^;;+[ \t]*" (ti::mail-pgp-msg-begin-line))
7574                nil t)
7575         (pop-to-buffer (current-buffer))
7576         (error "TinyLib: Can't find PGP beginning %s " source))
7577       (beginning-of-line)
7578       ;;  remove comments
7579       (delete-rectangle (point) end)
7580       ;;  Leave only the signed region, remove rest
7581       (delete-region (point-min) (point))
7582       (buffer-enable-undo)
7583       ;; .................................................... call pgp ...
7584       (setq cmd (format "%% rm %s %s\n"  in-file out-file))
7585       (unless test
7586         (ti::file-delete-safe (list in-file out-file)))
7587       (write-region (point-max) (point-min) in-file)
7588       (unless (file-exists-p in-file)
7589         (error "TinyLib: Writing PGP data failed to file %s" in-file))
7590       ;;  Write-file may have some strange modes, be sure we can read them
7591       ;;  384dec = 600oct
7592       (set-file-modes in-file (logior (file-modes in-file) 384))
7593       (erase-buffer)
7594       ;; Start showing the log to user
7595       (pop-to-buffer buffer)
7596       (insert cmd)
7597       (let* ((out-file          (ti::file-name-forward-slashes out-file))
7598              (default-directory (file-name-directory out-file))
7599              (file              (file-name-nondirectory out-file)))
7600         (insert (format "%% cd %s ; %s -o %s %s\n"
7601                         default-directory
7602                         pgp-bin
7603                         file
7604                         (file-name-nondirectory in-file)))
7605         (unless test
7606           (call-process pgp-bin
7607                         nil
7608                         buffer
7609                         nil
7610                         "-o" file (file-name-nondirectory in-file))
7611           (ti::pmin)
7612           (unless (re-search-forward "Plaintext filename:" nil t)
7613             (error "TinyLib: Can't proceed, PGP didn't set filename.")))
7614         ;; .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. show tar content  ..
7615         (ti::pmax)
7616         (setq cmd  (format "cd %s ; %s %s %s"
7617                            default-directory
7618                            tar
7619                            tar-opt-show
7620                            file))
7621
7622         (insert "% " cmd "\n") (setq beg (point))
7623         (unless test
7624           (call-process tar
7625                         nil buffer nil
7626                         tar-opt-show
7627                         file)
7628           (goto-char beg)
7629           (if (null (setq file-list (ti::process-tar-read-listing-forward)))
7630               (error "TinyLib: Can't find tar listing."))))
7631       ;; .. .. .. .. .. .. .. .. .. .. .. .. ..  previously installed?  ..
7632       (setq list file-list)
7633       (dolist (elt list)
7634         (setq in (concat dir (car elt)))
7635         (when (file-exists-p in)
7636           (if (y-or-n-p
7637                (format
7638                 "TinyLib: Previously installed file `%s'. Overwrite ? "
7639                 in))
7640               (unless test
7641                 (delete-file in))
7642             (error "Abort.")) ))
7643       (setq cmd  (format "cd %s ; tar %s %s"
7644                          (expand-file-name dir)
7645                          tar-opt-x
7646                          out-file))
7647       (insert "% "cmd "\n")
7648       (unless test
7649         (let* ((default-directory (expand-file-name dir)))
7650           (call-process tar nil buffer nil
7651                         tar-opt-x
7652                         (expand-file-name out-file))))
7653       ;; .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. . clean  ..
7654       (when (y-or-n-p "TinyLib: Clean up tmp files? ")
7655         (push in-file file-list)
7656         (push out-file file-list)
7657         (dolist (elt file-list)
7658           (insert (format "%% rm %s\n" elt))
7659           (unless test
7660             (ti::file-delete-safe elt) )))
7661       (message "TinyLib: installation to %s complete" dir))))
7662
7663 ;;}}}
7664 ;;{{{ misc: XEmacs compatibility
7665
7666 ;;; ----------------------------------------------------------------------
7667 ;;;
7668 (defun ti::compat-installation-root ()
7669   "Return XEmacs installation root directory without trailing slash.
7670 If this is queried unde Emacs, `exec-path' must contain XEmacs binary,
7671 otherwise `load-path' is conculted."
7672   (let* ((xemacs   (ti::xemacs-p))
7673          (ver      (if xemacs
7674                        (ti::emacs-version-number-as-string))) ;eg "19.14"
7675          match
7676          ret)
7677     (dolist (path (if xemacs
7678                       load-path
7679                     exec-path))
7680       ;;  When we find the version from the path, ve know the root
7681       ;;  directory
7682       ;;
7683       ;;  /opt/local/lib/xemacs-19.14/lisp/vms -->
7684       ;;  /opt/local/lib/xemacs-19.14/lisp/
7685       (when (and (stringp path)
7686                  (string-match "xemacs" path)
7687                  (if ver
7688                      ;; running under XEmacs, we know what to look for.
7689                      (setq match (ti::string-match
7690                                   (concat "^.*" ver) 0 path))
7691                    ;; Take a guess, anything that looks like XEmacs in path
7692                    (setq match
7693                          (ti::string-match
7694                           ;;  XEmacs-21.2.36/ or XEmacs/21.2.36/
7695                           "^\\(.*xemacs[-\\/][0-9]+\\.[0-9.]*[0-9]\\)[\\/]"
7696                           1 path))))
7697         (setq ret (concat match "/lisp"))
7698         (return)))
7699     ret))
7700
7701 ;;; ----------------------------------------------------------------------
7702 ;;;
7703 (defun ti::compat-overlay-some ()
7704   "Return some existing overlay that is used in Emacs.
7705 Usually the primary mouse selection. You can use this function to get an
7706 overlay that you can move in text if you don't want to create
7707 new overlay.
7708
7709 Return:
7710   overlay symbol"
7711   (cond
7712    ((and (ti::xemacs-p)
7713          (boundp 'primary-selection-extent))
7714     'primary-selection-extent)
7715    ((and (ti::emacs-p)
7716          (boundp 'mouse-drag-overlay))
7717     'mouse-drag-overlay)))
7718
7719 ;;; ----------------------------------------------------------------------
7720 ;;;
7721 (defun ti::compat-overlay-properties  (overlay)
7722   "Return properties of OVERLAY."
7723   (cond
7724    ((ti::overlay-supported-p)
7725     (ti::funcall 'overlay-properties overlay))
7726    ((ti::xemacs-p)
7727     (ti::funcall 'extent-properties overlay))))
7728
7729 ;;; ----------------------------------------------------------------------
7730 ;;;
7731 (defun ti::compat-overlays-at (point)
7732   "Return overlays at POINT."
7733   (cond
7734    ((ti::overlay-supported-p)
7735     (ti::funcall 'overlays-at point))
7736    ((ti::xemacs-p)
7737     (let* (list)
7738       (ti::funcall
7739        'map-extents
7740        (function (lambda (ov maparg) (push ov list)))
7741        (current-buffer) point point)
7742       list))))
7743
7744 ;;; ----------------------------------------------------------------------
7745 ;;;
7746 (defun ti::compat-overlay-put (ov-sym prop val)
7747   "Set properties to overlay OV-SYM. Put PROP VAL pair to OV-SYM."
7748   (cond
7749    ((ti::overlay-supported-p)
7750     (ti::funcall 'overlay-put (symbol-value ov-sym) prop val))
7751    ((ti::xemacs-p)
7752     (ti::funcall 'set-extent-property (symbol-value ov-sym) prop val))))
7753
7754 ;;; ----------------------------------------------------------------------
7755 ;;;
7756 (defun ti::compat-overlay-move (ov-sym beg end &optional make-local face)
7757   "Move overlay OV-SYM to BEG END. Overlay is created if it does not exist.
7758 MAKE-LOCAL localizes the overlay. If the overlay is created,
7759 then FACE is assigned to it (default 'highlight)"
7760   (cond
7761    ((ti::overlay-supported-p)
7762     ;; ................................................ create overlay ...
7763     ;;  later XEmacs may have overlay emulation
7764     (or (symbol-value ov-sym)           ;Exist?
7765         (progn
7766           (if make-local (make-local-variable ov-sym))
7767           (set ov-sym
7768                (ti::funcall 'make-overlay (point) (point)))
7769           (ti::funcall 'overlay-put
7770                        (symbol-value ov-sym)
7771                        'face (or face 'highlight))))
7772     ;; .......................................................... move ...
7773     (ti::funcall 'move-overlay (symbol-value ov-sym)
7774                  beg end (current-buffer)))
7775    ((ti::xemacs-p)
7776     (or (symbol-value ov-sym)           ;Exist?
7777         (progn
7778           (if make-local (make-local-variable ov-sym))
7779           (set ov-sym
7780                (ti::funcall 'make-extent (point) (point)))
7781           (ti::funcall 'set-extent-property
7782                        (symbol-value ov-sym)
7783                        'face (or face 'highlight))))
7784     (ti::funcall 'set-extent-endpoints
7785                  (symbol-value ov-sym)
7786                  beg end (current-buffer)))))
7787
7788 ;;; ----------------------------------------------------------------------
7789 ;;;
7790 (defun ti::compat-activate-region  (&optional off)
7791   "Activate region or turn the region OFF."
7792   (if (ti::emacs-p)
7793       (ti::funcall 'transient-mark-mode (if off 0 1)) ;From Simple.el
7794     (if off
7795         (ti::funcall 'zmacs-deactivate-region)
7796       (set 'zmacs-regions (if off nil t)) ;Avoid bute compile mesage in Emacs
7797       (ti::funcall 'activate-region))))
7798
7799 ;;; ----------------------------------------------------------------------
7800 ;;;
7801 (defun ti::compat-read-password  (&optional prompt)
7802   "Read password with PROMPT which defaults to 'Password: '."
7803   (let* ((var-bind  (boundp 'record-keystrokes))
7804          ;; If a GC occurred during that timing window, and a core dump was
7805          ;; forced later, the core might contain the string.
7806          ;;  --> use most-positive-fixnum
7807          (gc-cons-threshold (* 1024 1024))
7808          record-keystrokes)             ;XEmacs 20.4
7809     (setq prompt (or prompt "Password: "))
7810     (prog1
7811         (cond
7812          ((ti::xemacs-p)
7813           ;; if one follows the
7814           ;; - as soon as you are done with the returned string,
7815           ;;   destroy it with (fillarray string 0).
7816           ;;
7817           (require  'passwd)            ;utils/passwd.el
7818           (ti::funcall 'read-passwd prompt))
7819          (t
7820           ;;  Could also use (comint-read-noecho prompt)
7821           ;;  Comint won't echo anything.
7822           (ti::query-read-input-as-password prompt)))
7823       ;; ByteComp silencer; non used variable
7824       (if record-keystrokes
7825           (setq record-keystrokes nil))
7826       ;;  In old Emacs versions 19.35< and XEmacs 19.16< 20.3<
7827       ;;  you can actually read the password from lossage buffer with C-h l
7828       ;;
7829       ;;  --> We can clear it by filling it with 100 new characters.
7830       ;;      But this really works in XEmacs only, because Emacs
7831       ;;      Doesn't log events from macros.
7832       ;;
7833       (cond
7834        ((fboundp  'clear-lossage)
7835         (ti::funcall 'clear-lossage))
7836        ((fboundp  'clear-recent-keys)
7837         (ti::funcall 'clear-recent-keys))
7838        ((and (ti::xemacs-p)
7839              (not var-bind))
7840         (save-window-excursion
7841           (with-temp-buffer
7842             ;; force writing "1"  x 100 in this buffer
7843             ;;
7844             (switch-to-buffer (current-buffer))
7845             (ti::dotimes counter 1 100 (execute-kbd-macro "1")))))))))
7846
7847 ;;; ----------------------------------------------------------------------
7848 ;;;
7849 (defun ti::compat-key-local-map (key)
7850   "Return local map function for KEY"
7851   (let* ((prop      (text-properties-at (point)))
7852          (map       (and  prop
7853                           (nth 1 (memq 'keymap prop))))
7854          (function  (and  map
7855                           (lookup-key map key))))
7856     function))
7857
7858 ;;; ----------------------------------------------------------------------
7859 ;;;
7860 (defun ti::compat-key-call-original (minor-mode-symbol key-binding)
7861   "Turn of MINOR-MODE-SYMBOL and execute original KEY-BINDING.
7862 This won't work on mouse commands that examine the mouse `event'"
7863   (let* ((map           (or (current-local-map) global-map))
7864          (function      (lookup-key map key-binding))
7865          (this-command  (if function function this-command)))
7866     (when (and (not (ti::bool-p function))
7867                (symbolp function)
7868                (fboundp function))
7869       (unwind-protect
7870           (progn
7871             (put minor-mode-symbol 'ti::orig-value-key
7872                  (symbol-value minor-mode-symbol))
7873             (set minor-mode-symbol nil)
7874             ;;  This is very simplistic call. E.g. mouse event should
7875             ;;  be called with  (funcall function event)
7876             (call-interactively function)))
7877       ;; Make sure minor mode setting is restored
7878       (set minor-mode-symbol
7879            (get minor-mode-symbol 'ti::orig-value-key)))))
7880
7881 ;;; ----------------------------------------------------------------------
7882 ;;;
7883 (defun ti::compat-mouse-position-coordinates ()
7884   "Return '(LINE COLUMN) where mouse pointer is currently.
7885 If mouse is not supported, return nil."
7886   (when (fboundp 'mouse-position)
7887     (let ( ;; (frame (car (mouse-position)))
7888           (x  (cadr (mouse-position)))
7889           (y  (cddr (mouse-position))))
7890       ;;  window-list returns all windows starting from TOP. Count
7891       ;;  Lines in every window and compare that to mouse-position
7892       (let ((win (get-buffer-window (current-buffer)))
7893             (count 0))
7894         (save-window-excursion
7895           (dolist (elt (window-list))
7896             (when (eq elt win)
7897               (return))
7898             (select-window elt)
7899             ;;  Modeline is not counted as +1
7900             (setq count (+ count (window-height)))))
7901         ;; (ti::d! count x y)
7902         (list (1+ (- y count))
7903               ;;  In Emacs 21.x there is a "fringe" that mouse-position
7904               ;;  reports as X=0,
7905               (if (eq x 0)
7906                   ;; Consider "fringe" as column 0
7907                   0
7908                 ;; Removed "fringe" count
7909                 (1- x)))))))
7910
7911 ;;; ----------------------------------------------------------------------
7912 ;;;
7913 (defun ti::compat-mouse-key (event)
7914   "Return mouse key for EVENT."
7915   (cond
7916    ((ti::emacs-p)
7917     (make-vector 1 (car event)))
7918    ((ti::xemacs-p)
7919     (vector
7920      (append (event-modifiers event)
7921              (list (intern
7922                     (format
7923                      "button%d"
7924                      (ti::funcall 'event-button event)))))))))
7925
7926 ;;; ----------------------------------------------------------------------
7927 ;;;
7928 (defun ti::compat-mouse-call-original-function (minor-mode-symbol &optional event)
7929   "Return original function behind MINOR-MODE-SYMBOL with mouse EVENT.
7930 See. `ti::-xe-mouse-call-original'."
7931   (let* (ret
7932          flyspell-p)
7933     (or event
7934         (setq event last-input-event))
7935     (when (or (null minor-mode-symbol)
7936               (not (symbolp minor-mode-symbol))
7937               (not (boundp minor-mode-symbol)))
7938       (error "Invalid minor-mode-symbol `%s'." minor-mode-symbol))
7939     ;;  Turn off minor mode, so that we can see the real
7940     ;;  function behind it.
7941     (put minor-mode-symbol 'ti::orig-value (symbol-value minor-mode-symbol))
7942     (set minor-mode-symbol nil)
7943     ;; Unfortunately if flyspell is active (mouse-2 binding), ir does not look
7944     ;; key definition of mouse-2, but a `this-command-keys',
7945     ;; which is not correct.
7946     ;; => Turn off flyspell if there is no flyspell overlay underneath
7947     (when (and (boundp 'flyspell-mode)
7948                flyspell-mode
7949                (fboundp 'flyspell-overlay-p)
7950                (not (ti::funcall 'flyspell-overlay-p (overlays-at (point)))))
7951       (put minor-mode-symbol 'ti::orig-value-flyspell flyspell-mode)
7952       (setq flyspell-p t)
7953       (setq flyspell-mode nil))
7954     (setq ret (key-binding (ti::compat-mouse-key event))) ;Read it
7955     ;; Restore active modes
7956     (when flyspell-p
7957       (put minor-mode-symbol 'ti::orig-value-flyspell flyspell-mode))
7958     (set minor-mode-symbol (get minor-mode-symbol 'ti::orig-value))
7959     ret))
7960
7961 ;;; ----------------------------------------------------------------------
7962 ;;;
7963 (defvar ti::-xe-mouse-call-original nil "See ti::keymap-mouse-call-original.")
7964
7965 (defun ti::compat-mouse-call-original (minor-mode-symbol &optional event)
7966   "Execute original mouse function by turning of MINOR-MODE-SYMBOL.
7967 EVENT is mouse event. You use this function to to handle 'hot spots' in the
7968 buffer and in other places you call the original function.
7969
7970 Do nothing if original function does not exist.
7971 Does nothing when called by a function which has earlier been called
7972 by us.
7973
7974 Example for some minor mode implementation:
7975
7976 ext-pro  (defun folding-mode-context-sensitive  (event)
7977     (interactive \"e\")
7978     ;; If test.. if test..no, then call original function
7979     (ti::compat-mouse-call-original 'folding-mode event))
7980
7981 Note:
7982
7983   Works in XEmacs and Emacs
7984
7985 Sets global:
7986
7987   `ti::-xe-mouse-call-original'"
7988   ;; Without the following test we could easily end up in a endless
7989   ;; loop in case we would call a function which would call us.
7990   (if ti::-xe-mouse-call-original ;; We're looping already
7991       nil
7992     (setq ti::-xe-mouse-call-original t)
7993     (unwind-protect
7994         (let* ((orig-buf (current-buffer))
7995                (mouse-func (ti::compat-mouse-call-original-function
7996                             minor-mode-symbol event))
7997                (local-func (ti::compat-key-local-map
7998                             (ti::compat-mouse-key event)))
7999                (orig-func  (or local-func
8000                                mouse-func))
8001                (event-p    (when orig-func
8002                              (string-match
8003                               "event"
8004                               (or (ti::function-args-p orig-func)
8005                                   "")))))
8006           (when orig-func
8007             ;;  Only if existed
8008             ;;  call it with the event as argument.
8009             ;;  We have to restore the current buffer too, because
8010             ;;  the minor mode is there.
8011             (put minor-mode-symbol 'ti::orig-value
8012                  (symbol-value minor-mode-symbol))
8013             (unwind-protect
8014                 (if event-p
8015                     (funcall orig-func event)
8016                   ;;  Try direct call first, or pass the EVENT
8017                   (or (eq 'done (progn (call-interactively orig-func) 'done))
8018                       (eq 'done (progn (funcall orig-func event) 'done))))
8019               (set-buffer orig-buf)
8020               (set minor-mode-symbol (get minor-mode-symbol
8021                                           'ti::orig-value)))))
8022       ;; .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..  unwind  ..
8023       ;; This is always executed, even if the above generates an error.
8024       (setq ti::-xe-mouse-call-original nil))))
8025
8026 ;;; ----------------------------------------------------------------------
8027 ;;;
8028 (defun ti::compat-popup (string-list &optional event mode title)
8029   "Show STRING-LIST pop up. If EVENT is nil, use default tinylib coordinates.
8030 Works in XEmacs and Emacs.
8031
8032 Input:
8033
8034   STRING-LIST   '(str str ..)
8035   EVENT         mouse-event or nil
8036   MODE          if non-nil, return selection NBR [0..n]. Normally
8037                 returns the selection itself.
8038   TITLE         title of popup
8039
8040 Return:
8041
8042   selection     member or nbr
8043   nil           nothing selected"
8044   (interactive "e")
8045   (let* ((title  (or title ""))
8046          (count  0)
8047          ;;  Allow calling from key press also.
8048          (event  (or event
8049                      (ti::compat-make-x-popup-event
8050                       ti::var-x-coord  ti::var-y-coord)))
8051          menu
8052          item-list
8053          alist
8054          ret)
8055     (when (ti::listp string-list)
8056       (setq alist  (ti::list-to-assoc-menu string-list))
8057       (cond
8058        ((ti::emacs-p)
8059         (setq item-list  alist)
8060         (setq menu
8061               (cons title
8062                     (list (cons title item-list))))
8063         (if (fboundp 'x-popup-menu)
8064             (setq ret (ti::funcall 'x-popup-menu  event menu)))
8065         (if ret
8066             (if (null mode)
8067                 (setq ret (nth ret string-list)))))
8068        (t
8069         ;; Scenario: User selects item from menu-bar-menu which calls
8070         ;; function that should be called from mouse press --> selecting
8071         ;; from pull-down-menu, is not a mouse event!
8072         ;;
8073         ;; First one is real mouse call for function; the other one
8074         ;; is called from popup selection
8075         ;;
8076         ;;      #<buttondown-event button1>
8077         ;;      #<misc-user-event (call-interactively tig-index-x-popup)>
8078         ;;
8079         ;; get-popup-menu-response call breaks if EVENT is something
8080         ;; else than mouse-event. Check it immediately and set EVENT
8081         ;; to nil, because the parameter is optional.
8082         (if (and event (null (ti::funcall 'mouse-event-p event)))
8083             (setq event nil))
8084         ;;  Menu format is like this in XEmacs
8085         ;;
8086         ;; '("title" ["A" ("A") t] ["B" ("B") t] ["C" ("C") t]
8087         (setq item-list string-list)
8088         (setq menu
8089               (mapcar
8090                (function
8091                 (lambda (x &optional vec)
8092                   (setq vec (make-vector 3 nil))
8093                   (aset vec 0 x)
8094                   (aset vec 1 (list x))
8095                   (aset vec 2 t)
8096                   vec))
8097                item-list))
8098         (setq menu (push title menu))
8099         ;; #todo, I don't know why there is nothing in the RET
8100         ;; after the selection has been done...
8101         ;;  See menubar.el
8102         ;;
8103         (setq ret (ti::funcall 'get-popup-menu-response  menu event ))
8104         (if (ti::funcall 'misc-user-event-p ret)
8105             (setq ret (car-safe (ti::funcall 'event-object  ret))))
8106         (when (and ret mode)            ;find position in list
8107           (dolist (arg menu)
8108             (when (and (vectorp arg)
8109                        (string= ret (elt arg 0)))
8110               (setq ret  (1- count))
8111               (return))
8112             (incf count))))))
8113     ret))
8114
8115 ;;; ----------------------------------------------------------------------
8116 ;;;
8117 (defun ti::compat-display-depth  ()
8118   "Return how many colors display can show."
8119   (cond
8120    ((ti::emacs-p)
8121     (ti::funcall 'x-display-planes (symbol-value 'x-display-name)))
8122    (t
8123     (ti::funcall 'device-bitplanes (ti::funcall 'default-x-device)))))
8124
8125 ;;; ----------------------------------------------------------------------
8126 ;;;
8127 (defun ti::compat-read-event ()
8128   "Read X event."
8129   (cond
8130    ((ti::emacs-p)
8131     (if (fboundp 'event-to-character)
8132         (ti::funcall 'read-event)
8133       (error "Cannot read events.")))
8134    (t
8135     (ti::funcall 'next-command-event))))
8136
8137 ;;; ----------------------------------------------------------------------
8138 ;;;
8139 (defun ti::compat-executing-macro ()
8140   "Check if executing macro."
8141   (cond
8142    ((boundp 'executing-macro)
8143     (symbol-value 'executing-macro))    ;Emacs and old XEmacs
8144    ((boundp 'executing-kbd-macro)       ;New XEmacs
8145     (symbol-value 'executing-kbd-macro))))
8146
8147 ;; briefly: events in 19.28, see subr.el
8148 ;; -------------------------------------------
8149 ;; event       :(mouse-3 (#<window 34 on *scratch*> 128 (20 . 104) -23723628))
8150 ;;
8151 ;; (setq event-start event)
8152 ;; event-start :(#<window 34 on *scratch*> 128 (20 . 104) -23723628))
8153 ;;                                         |   |          time
8154 ;;                               mouse point   coordinates
8155 ;;
8156 ;; (setq posn-col-row event-start) --> turn (20 . 104) into (col row)
8157 ;;
8158 (defun ti::compat-make-x-popup-event (x y)
8159   "Make fake EVENT using X and Y coordinates.
8160 Very handy if you call from kbd a function that requires mouse event."
8161   (cond
8162    ((ti::emacs-p)
8163     (list (list x  y) (selected-window)))
8164    (t
8165     ;;; (message "ti::compat-make-x-popup-event, XEmacs implementation not known.")
8166     nil)))
8167
8168 ;;; ----------------------------------------------------------------------
8169 ;;;
8170 (defun ti::compat-make-fake-event (x y &optional mouse-sym)
8171   "Make fake event using X and Y coordinates and MOUSE-SYM[mouse - 1].
8172
8173 Remeber: this is not full blown fake, just sufficent one, if
8174 receiver uses any of 'posn-' function, this doesn't fool it."
8175
8176   ;; (mouse-1 (#<window 42 on tinylib.el> 271088 (92 . 138) -492011))
8177   (cond
8178    ((ti::emacs-p)
8179     (list
8180      (or mouse-sym 'mouse-1 )
8181      (list
8182       (selected-window)
8183       1                                 ;<just some calue>
8184       (cons x y )
8185       -23723628)))
8186    (t
8187     ;; (message "ti::compat-make-fake-event, XEmacs implementation not known.")
8188     ;;
8189     ;; You can't create fake events in XEmacs.  The object data is
8190     ;; hidden behind an abstraction layer and there are no functions to
8191     ;; build or modify event objects.  You can only allocate and copy
8192     ;; them.
8193     ;;
8194     nil)))
8195
8196 ;;; ----------------------------------------------------------------------
8197 ;;;
8198 (defun ti::compat-modeline-update ()
8199   "XEmacs and Emacs Compatibility. Update modeline."
8200   (cond
8201    ((and (ti::xemacs-p)
8202          (fboundp 'redraw-modeline))
8203     ;; Xe 19.14
8204     ;; force-mode-line-update is an obsolete function; use redraw-modeline
8205     (ti::funcall 'redraw-modeline))
8206    ((fboundp 'force-mode-line-update)
8207     (ti::funcall 'force-mode-line-update))
8208    (t
8209     (set-buffer-modified-p (buffer-modified-p)))))
8210
8211 ;;; ----------------------------------------------------------------------
8212 ;;; - Changing the frame label is same as changing the icon label
8213 ;;;
8214 (defun ti::compat-set-frame-parameter (prop-or-list value &optional frame)
8215   "Use PROP-OR-LIST and VALUE to set FRAME's parameters.
8216 When called interactively, set name of the frame.
8217
8218 Input:
8219  PROP-OR-LIST       alist of parameters or single property name
8220                     '((param . val) ..)
8221  VALUE              only used if single property given.
8222  FRAME              defaults to current frame."
8223   (interactive
8224    (list
8225     'name
8226     (read-from-minibuffer "frame label name: ")))
8227   (let* ((frame (or frame (selected-frame))))
8228     (cond
8229      ((and (ti::xemacs-p)
8230            (fboundp 'set-frame-properties))
8231       ;; #todo:  Why don't these work in XEmacs 19.14 ?
8232       (if (ti::listp prop-or-list)
8233           (ti::funcall 'set-frame-properties frame prop-or-list)
8234         (ti::funcall 'set-frame-property frame prop-or-list value)))
8235      (t
8236       (if (not (ti::listp prop-or-list))
8237           (setq prop-or-list (list (cons prop-or-list value))))
8238       (ti::funcall 'modify-frame-parameters frame prop-or-list)))))
8239
8240 ;;; ----------------------------------------------------------------------
8241 ;;;
8242 (defun ti::compat-set-frame-name (string &optional frame get)
8243   "Change the frame display STRING in FRAME.
8244 The implementation works differently in various emacs versions.
8245
8246 If GET is non-nil return frame name."
8247   (let* ((symbol 'name))
8248     (when (ti::emacs-p)
8249       ;; somewhere along the line the symbol was renamed to 'title
8250       ;; #todo: 19.31 - 33, frame, Would someone confirm this?
8251       (when (and (> emacs-minor-version 31)
8252                  (< emacs-minor-version 34))
8253         (setq symbol 'title)))
8254     (if get
8255         (frame-parameter frame symbol)
8256       (ti::compat-set-frame-parameter symbol string frame))))
8257
8258 ;;; ----------------------------------------------------------------------
8259 ;;;
8260 (defun ti::compat-frame-window-config ()
8261   "Return list '((FRAME WINDOW-CONFIGURATION) (F W) ..)."
8262   (let (ret)
8263     (dolist (elt
8264              (cdr (current-frame-configuration)))
8265       (push (list (nth 0 elt) (nth 2 elt))  ret))
8266     (nreverse ret)))
8267
8268 ;;; ----------------------------------------------------------------------
8269 ;;; XEmacs 19.14 "window-system is an obsolete variable; use (console-type)"
8270 ;;;
8271 (defun ti::compat-window-system  ()
8272   "XEmacs and Emacs Compatibility, Mimic Emacs `window-system' variable.
8273 In XEmacs the `cosole-type' returns 'tty on terminal, but this function
8274 return nil to be in par with Emacs behavior. An 'tty is not a windowed
8275 environment."
8276   (cond
8277    ((fboundp 'console-type)
8278     (let ((val (ti::funcall 'console-type)))
8279       (unless (eq 'tty val)
8280         val)))
8281    ((boundp 'window-system)
8282     (symbol-value 'window-system))))
8283
8284 ;;; ....................................................... &xe-timers ...
8285
8286 ;;; ----------------------------------------------------------------------
8287 ;;;
8288 (defun ti::compat-timer-list-control (&optional mode)
8289   "Timer handling: MODE can be 'save 'restore or 'kill.
8290
8291 Example:
8292
8293   ;; Turn off all processes for a while...
8294
8295   (ti::compat-timer-list-control 'save)
8296   (ti::compat-timer-list-control 'kill)
8297
8298   ;; ... do something
8299
8300   ;;  Now restore the prosesses
8301
8302   (ti::compat-timer-list-control 'restore)"
8303
8304   (let* ((sym
8305           (cond
8306            ((boundp 'timer-alist)  'timer-alist)
8307            ((boundp 'timer-list)   'timer-list)
8308            ((boundp 'itimer-list)  'itimer-list))))
8309     ;;  We store/restore the list into the timer variable symbol
8310     ;;  properties.
8311     (cond
8312      ((eq 'kill mode)
8313       (set sym nil))
8314      ((eq 'save mode)
8315       (put sym 'ti::saved (symbol-value sym)))
8316      ((eq 'restore mode)
8317       (set sym (get sym 'ti::saved))))))
8318
8319 ;;; ----------------------------------------------------------------------
8320 ;;;
8321 ;;;###autoload
8322 (defun ti::compat-timer-control
8323   (&optional time repeat function delete verb)
8324   "With `run-at-time' TIME REPEAT FUNCTION keep or remove timer. VERB."
8325   (let* (timer)
8326     (ti::verb)
8327     (ti::compat-timer-cancel-function function)
8328     (cond
8329      (delete
8330       (if verb (message "TinyLib: timer process %s removed." function)))
8331      (t
8332       ;; this will also restart timer
8333       ;; In Emacs 19.28 - 19.30 , you could pass parameter
8334       ;; "now", but later emacs releases do not accept it.
8335       ;;
8336       (setq timer
8337             (run-at-time time repeat function))
8338
8339       (if verb
8340           (message "TinyScroll: timer process started."))))
8341     timer))
8342
8343 ;;; ----------------------------------------------------------------------
8344 ;;;
8345 (defun ti::compat-timer-elt  (function)
8346   "Search FUNCTION and return timer elt.
8347 You can use this function to check if some function is currently
8348 in timer list. (ie. active)
8349
8350 The timer lists are searched in following order:
8351
8352   `itimer-list'
8353   `timer-list'
8354   'timer-idle-list'
8355
8356 Return:
8357
8358   '(timer-elt timer-variable)"
8359   (let* (pos
8360          list
8361          item
8362          ret)
8363     (flet ((get-elt (elt place)
8364                     (if (vectorp elt)
8365                         (aref elt place)
8366                       (nth place elt))))
8367       (dolist (timer '( ;; (("Mon Dec  9 10:01:47 1996-0" 10 tipgp-process nil))
8368                        (timer-idle-list . 5)
8369                        (timer-alist . 2)
8370                        (timer-list  . 2) ;; 19.34+
8371                        (itimer-list . 3)))
8372         (when (boundp (car timer))
8373           (setq list (symbol-value (car timer))
8374                 pos  (cdr timer))
8375           ;;  NOTE: this is different in Xemacs. It is not a vector
8376           ;; timer-[idle-]list Emacs 19.34
8377           ;;  NOTE: this is different in Xemacs. It is not a vector
8378
8379           ;; ([nil 12971 57604 0 60 display-time-event-handler nil nil])
8380           ;; [nil 13971 14627 646194 60
8381           ;;      (lambda (f) (run-at-time ...))
8382           ;;      (irchat-Command-keepalive) nil]
8383           (if (and (ti::emacs-p)
8384                    (vectorp (car list)))
8385               (setq pos 5))
8386           (dolist (elt list)
8387             (setq item (get-elt elt pos))
8388             (when (or (and (symbolp item)
8389                            (eq item function))
8390                       ;;  It may be lambda expression
8391                       (and (functionp item)
8392                            (string-match (regexp-quote (symbol-name function))
8393                                          (prin1-to-string
8394                                           (get-elt elt (1+ pos))))))
8395               (setq ret (list elt (car timer)))
8396               (return))))))
8397     ret))
8398
8399 ;;; ----------------------------------------------------------------------
8400 ;;;
8401 (defun ti::compat-timer-process-status ()
8402   "XEmacs and Emacs Compatibility. Return timer process status: t if active."
8403   (cond
8404    ((boundp 'timer-alist)               ;Emacs
8405     (symbol-value 'timer-process))
8406    ((boundp 'timer-list)                ;Emacs 19.34
8407     (ti::compat-timer-elt  'display-time-event-handler))
8408    ((boundp 'itimer-list)               ;
8409     ;; it is built in in XEmacs
8410     t)))
8411
8412 ;;; ----------------------------------------------------------------------
8413 ;;;
8414 (defun ti::compat-timer-cancel  (key &optional cancel-function)
8415   "Delete timer KEY entry, where KEY is full element in (i)`timer-alist'.
8416 Function `ti::compat-timer-cancel-function' may be more what you want
8417 if you know the function in timer list."
8418   (let (var)
8419     (if (null key)
8420         nil                             ;Do nothing
8421       (when (and (null var)
8422                  (boundp 'timer-alist)) ;Emacs
8423         (setq var 'timer-alist)
8424         (ti::funcall 'cancel-timer key)
8425         (set var (delete key (symbol-value 'timer-alist))))
8426
8427       (when (and (null var)
8428                  (boundp 'timer-list))  ;Emacs 19.34
8429         (setq var 'timer-list)
8430         ;;  Must use this command
8431         (ti::funcall 'cancel-timer key))
8432       (when (and (null var)
8433                  (boundp 'timer-idle-list)) ;Emacs 19.34
8434         (setq var 'timer-idle-list)
8435         ;;  Must use this command
8436         (ti::funcall 'cancel-timer key))
8437       (when (and (null var)
8438                  (boundp 'itimer-list)) ;XEmacs
8439         (setq var 'itimer-list)
8440         (ti::funcall 'cancel-itimer key)
8441         (set var (delete key (symbol-value 'itimer-list))))
8442       var)))
8443
8444 ;;; ----------------------------------------------------------------------
8445 ;;;
8446 (defun ti::compat-timer-cancel-function (function)
8447   "Delete all timer entries for FUNCTION."
8448   (let (key
8449         ret)
8450     (while (setq key (car-safe (ti::compat-timer-elt function)))
8451       (push key ret)
8452       (ti::compat-timer-cancel key))
8453     ret))
8454
8455 ;;; ----------------------------------------------------------------------
8456 ;;;
8457 (defun ti::compat-set-mode-line-format  (fmt)
8458   "Set modeline format using FMT."
8459   (let* ((sym
8460           (if (ti::emacs-p)
8461               'mode-line-format
8462             'modeline-format)))
8463     ;; XEmacs 19.14 says:
8464     ;; ** mode-line-format is an obsolete var; use modeline-format instead.
8465     (set sym fmt)))
8466
8467 ;;}}}
8468 ;;{{{ misc: create standard functions, variables
8469
8470 ;;; .......................................................... &fmacro ...
8471
8472 ;;; ----------------------------------------------------------------------
8473 ;;;
8474 (defmacro ti::macrov-minor-mode
8475   (pfx
8476    mode-Name
8477    mode-Name-prefix-key
8478    easymenu-Name
8479    custom-group
8480
8481    &optional style)
8482   "Return standard minor mode variables.
8483 See below how to call this function  from the top of your minor mode package.
8484
8485 Input:
8486
8487   PFX                   string, the package prefix, usually one or two
8488                         words. E.g. \"xxx\" or \"xxx-mode\"
8489   MODE-NAME             string; which is displayed in modeline, should have
8490                         leading space. E.g. \" Lisp\"
8491   MODE-NAME-PREFIX-KEY  string, Key sequences to access the minor mode
8492                         functions.
8493   EASYMENU-NAME         string, the Menu bar name string.
8494   CUSTOM-GROUP          symbol, the defcustom.el group name.
8495   PREFIX-STYLE          string, How the characters should be named.
8496                         if nil then uses standard Emacs naming.
8497
8498 Example, when:
8499
8500   PFX               is \"xxx-\"
8501   STYLE             is nil              ;; Standard Emacs style
8502
8503   (defvar xxx-mode                      nil)
8504   (make-variable-buffer-local           'xxx-mode)
8505
8506   (defvar xxx-mode-name                 MODE-NAME)
8507   (defvar xxx-mode-prefix-key           MODE-NAME-PREFIX-KEY)
8508   (defvar xxx-mode-map                  nil)
8509   (defvar xxx-mode-prefix-map           nil)
8510   (defvar xxx-mode-define-keys-hook     nil)
8511   (defvar xxx-mode-hook                 nil)
8512   (defvar xxx-mode-easymenu             nil)
8513   (defvar xxx-mode-easymenu-name        nil)
8514
8515 Example, when:
8516
8517   PFX               is \"xxx\"
8518   STYLE             is 'xxx-:
8519
8520   (defvar xxx-mode                      nil)
8521   (make-variable-buffer-local           'xxx-mode)
8522
8523   (defvar xxx-:mode-name                MODE-NAME)
8524   (defvar xxx-:mode-prefix-key          MODE-NAME-PREFIX-KEY)
8525   (defvar xxx-:mode-map                 nil)
8526   (defvar xxx-:mode-prefix-map          nil)
8527   (defvar xxx-:mode-define-keys-hook    nil)
8528   (defvar xxx-:mode-hook                nil)
8529   (defvar xxx-:mode-easymenu            nil)
8530   (defvar xxx-:mode-easymenu-name       nil)
8531
8532 How to call this function:
8533
8534   (ti::macrov-minor-mode \"xxx\" \" Xmode\" \"C-cx\" \"Xmenubar\" nil)"
8535   (` (, (ti::macrov-minor-mode-1
8536          pfx
8537          mode-Name
8538          mode-Name-prefix-key
8539          easymenu-Name
8540          custom-group
8541          style))))
8542
8543 ;;; ----------------------------------------------------------------------
8544 ;;;
8545 (defun ti::macrov-minor-mode-1
8546   (pfx
8547    mode-Name
8548    mode-Name-prefix-key
8549    easymenu-Name
8550    custom-group
8551
8552    &optional prefix-style)
8553   "Use `ti::macrov-minor-mode' and see call arguments there.
8554 PFX MODE-NAME MODE-NAME-PREFIX-KEY
8555 EASYMENU-NAME CUSTOM-GROUP PREFIX-STYLE"
8556   (let* ((x "-")
8557          sym
8558          ret)
8559     (if prefix-style
8560         (if (not (stringp prefix-style))
8561             (error "style must be string")
8562           (setq x prefix-style))
8563       (setq x pfx))
8564 ;;;    (push 'progn ret)
8565     ;; Force seeing variables at compile time
8566     ;;
8567     ;; Note 97-09-27
8568     ;; Thee started to appear errors from easymenu define command and
8569     ;; after byte compiler was forced to see the defvar definitions
8570     ;; of the variables during compile time, the compile was clean again.
8571     ;;
8572     ;; This was very odd.
8573     ;;
8574     ;;  (easy-menu-define
8575     ;;   tdi-:mode-easymenu
8576     ;;   tdi-:mode-map               << if not defvar seen, gives error
8577     ;;   "Elp summary sort menu."
8578     ;;   nil
8579     ;;   )
8580     (push 'eval-and-compile ret)
8581     (setq sym (intern (format "%smode" pfx)))
8582     (push (list 'defvar (` (, sym)) nil
8583                 "mode on off variable.")
8584           ret)
8585     (push (list 'make-variable-buffer-local (` (quote (, sym)))) ret)
8586
8587     (setq sym (intern (format "%smode-name" x)))
8588     (push (list 'defcustom (` (, sym))
8589                 (` (, mode-Name))
8590                 "*Minor mode name."
8591                 ':type ''string
8592                 ':group (` (, custom-group)))
8593           ret)
8594     (setq sym (intern (format "%smode-prefix-key" x)))
8595     (push (list 'defcustom (` (, sym))
8596                 (` (, mode-Name-prefix-key))
8597                 "*Prefix key to access mode."
8598                 ':type ''(string :tag "Key sequence")
8599                 ':group (` (, custom-group)))
8600           ret)
8601     (setq sym (intern (format "%smode-map" x)))
8602     (push (list 'eval-and-compile
8603                 (list
8604                  'defvar (` (, sym))
8605                  nil
8606                  "Minor mode map."))
8607           ret)
8608     (setq sym (intern (format "%smode-prefix-map" x)))
8609     (push (list 'eval-and-compile
8610                 (list
8611                  'defvar (` (, sym))
8612                  nil
8613                  "Prefix minor mode map."))
8614           ret)
8615     (setq sym (intern (format "%smode-easymenu" x)))
8616     (push (list 'defvar (` (, sym))
8617                 nil
8618                 "Easymenu variable.")
8619           ret)
8620     (setq sym (intern (format "%smode-easymenu-name" x)))
8621     (push (list 'defcustom  (` (, sym))
8622                 (` (, easymenu-Name))
8623                 "*Easymenu name that appears in menu-bar."
8624                 ':type ''string
8625                 ':group (` (, custom-group)))
8626           ret)
8627     (setq sym (intern (format "%smode-define-keys-hook" x)))
8628     (push (list 'defcustom (` (, sym))
8629                 nil
8630                 "*Hook that defines all keys and menus."
8631                 ':type ''hook
8632                 ':group (` (, custom-group)))
8633           ret)
8634     (setq sym (intern (format "%smode-hook" x)))
8635     (push (list 'defcustom (` (, sym))
8636                 nil
8637                 "*Hook that runs when mode function is called."
8638                 ':type ''hook
8639                 ':group (` (, custom-group)))
8640           ret)
8641     (nreverse ret)))
8642
8643 ;;; ----------------------------------------------------------------------
8644 ;;;
8645 (defmacro ti::macrof-minor-mode
8646   (func-min-sym
8647    doc-str
8648
8649    install-func                         ;3
8650    mode-var
8651    mode-Name                            ;5
8652    prefix-var
8653    menu-var                             ;7
8654
8655    no-mode-msg
8656    mode-desc                            ;9
8657
8658    hook
8659    &optional body)
8660   "Create standard functions for minor mode.
8661
8662 Input:
8663
8664   FUNC-MIN-SYM  symbol, the name of the function that is created.
8665                 E.g. 'xxx-mode
8666
8667   DOC-STR       string, the function documentation string
8668
8669   INSTALL-FUNC  symbol, if func-min-sym isn't in `minor-mode-alist', this
8670                 function is called to install the minor mode.
8671
8672   MODE-VAR      symbol, a variable which turns minor mode on or off
8673   MODE-NAME     symbol, a variable, contains mode name.
8674   [PREFIX-VAR]  symbol, a variable, mode's prefix key. Can be nil
8675   [MENU-VAR]    symbol, a variable, mode's menu definitions. The menu must be
8676                 in format of easy-menu.el so that it is Emacs and
8677                 XEmacs compatible
8678
8679   [NO-MODE-MSG] if non-nil, then default mode turn on or off message
8680                 is not displayed. The default message is
8681                 'MODE-DESC mode minor mode is ON. Prefix key is XXX'
8682   MODE-DESC     string, used in the default turn on message, see above.
8683
8684   [HOOK]        symbol, hook that is run when mode is called.
8685
8686   [BODY]        Lisp code to be added inside middle body. Can be nil.
8687
8688 Created function's arguments:
8689
8690   (&optional arg verb)
8691   ARG           is mode on off variable. nil toggles mode.
8692   VERB          is set in interactive call and controlls printing mode
8693                 turn on or off message. If nil, then no messages are
8694                 displayed.
8695
8696 Example how to use this macro:
8697
8698   ;;; We have to inform autoload that function exist after macro
8699   ;;;###autoload (autoload 'xxx-mode          \"package-file\" t t)
8700
8701   (ti::macrof-minor-mode
8702    xxx-mode
8703    \"XXX minor mode. This helps you to do ....
8704
8705   Defined keys:
8706   \\\\{xxx-mode-prefix-map}
8707   \"
8708     xxx-install-mode
8709     xxx-mode
8710     xxx-:mode-name
8711     xxx-:mode-prefix-key
8712     nil                     ;; no menu variables
8713     nil
8714     \"XXX\"
8715     xxx-:mode-hook
8716     ;; The forms
8717     ;;
8718     (progn
8719       (message \"Hey!\")))
8720
8721 Example how to call created functions:
8722
8723   (xxx-mode)            ;; toggles
8724   (xxx-mode 1)          ;; on
8725   (xxx-mode 0)          ;; off, could also be -1
8726   (turn-on-xxx-mode)    ;; function can be put to hook
8727   (turn-off-xxx-mode)"
8728   (` (,
8729       (ti::macrof-minor-mode-1
8730        func-min-sym
8731        doc-str
8732
8733        install-func
8734        mode-var
8735        mode-Name
8736        prefix-var
8737        menu-var
8738
8739        no-mode-msg
8740        mode-desc
8741
8742        hook
8743        body))))
8744
8745 ;;; ----------------------------------------------------------------------
8746 ;;;
8747 (defun ti::macrof-minor-mode-1
8748   (func-min-sym
8749    doc-str                              ;1
8750
8751    install-func                         ;2
8752    mode-var                             ;3
8753    mode-Name                            ;4
8754    prefix-var                           ;5
8755    menu-var                             ;6
8756
8757    no-mode-msg                          ;7
8758    mode-desc                            ;8
8759
8760    hook                                 ;9
8761    &rest body)                          ;10
8762   "Use macro `ti::macrof-minor-mode'. And see arguments there.
8763 FUNC-MIN-SYM DOC-STR INSTALL-FUNC MODE-VAR
8764 MODE-NAME PREFIX-VAR MENU-VAR NO-MODE-MSG MODE-DESC
8765 HOOK BODY"
8766
8767 ;;;  (ti::d!! "\n\n" body)
8768   (let* ((sym
8769           (intern (symbol-name (` (, func-min-sym)))))
8770          (viper-sym
8771           (intern (concat (symbol-name (` (, func-min-sym)))
8772                           "-viper-attach"))))
8773     (`
8774      (defun (, sym)
8775        (&optional arg verb)
8776        (, doc-str)
8777        (interactive "P")
8778        (ti::verb)
8779        (if (null (assq (quote (, func-min-sym)) minor-mode-alist))
8780            ((, install-func)))
8781 ;;;       (let* ((val (symbol-value  (, mode-var)))
8782 ;;;              )
8783 ;;;         (setq  (, mode-var) (ti::bool-toggle val arg)))
8784        (ti::bool-toggle (, mode-var) arg)
8785        ;;  XEmacs needs this call, in emacs turning on the minor
8786        ;;  mode automatically adds the menu too.
8787        ;;
8788 ;;;       (if (symbol-value (, mode-var))
8789 ;;;           (easy-menu-add (symbol-value (, menu-var)))
8790 ;;;         (easy-menu-remove (symbol-value (, menu-var))))
8791        (if (and (, mode-var)
8792                 (, menu-var))
8793            ;;  easy-menu-add dies if menu-var is nil
8794            (easy-menu-add (, menu-var))
8795          (easy-menu-remove (, menu-var)))
8796        (when (, mode-var)
8797          (funcall (quote (, viper-sym))))
8798        (,@ body)
8799        (ti::compat-modeline-update)
8800        (if (and verb (null (, no-mode-msg)))
8801            (message
8802             "%s minor mode is %s %s"
8803             (, mode-desc)
8804             (if  (, mode-var) "on." "off.")
8805             (if  (null (, mode-var))
8806                 ""
8807               (if (, prefix-var)
8808                   (format "Prefix key is %s" (, prefix-var))
8809                 ""))))
8810        (run-hooks (quote (, hook)))
8811        ;;  Return status of minor mode as last value.
8812        (, mode-var)))))
8813
8814 ;;; ----------------------------------------------------------------------
8815 ;;;
8816 (defun ti::macrof-minor-mode-on (mode-func-sym)
8817   "Create standard function to turn on the minor mode MODE-FUNC-SYM."
8818   (let* ((sym
8819           (intern (concat "turn-on-" (symbol-name (` (, mode-func-sym)))))))
8820     (`
8821      (defun (, sym) ()
8822        "Turn minor mode on"
8823        (interactive)
8824        ((, mode-func-sym) 1)))))
8825
8826 ;;; ----------------------------------------------------------------------
8827 ;;;
8828 (defun ti::macrof-minor-mode-off (mode-func-sym)
8829   "Create standard function to turn off the minor mode MODE-FUNC-SYM."
8830   (let* ((sym
8831           (intern (concat "turn-off-" (symbol-name (` (, mode-func-sym)))))))
8832     (`
8833      (defun (, sym) ()
8834        "Turn minor mode off"
8835        (interactive)
8836        ((, mode-func-sym) -1)))))
8837
8838 ;;; ----------------------------------------------------------------------
8839 ;;;
8840 (defun ti::macrof-minor-mode-help (mode-func-sym)
8841   "Create standard function to print MODE-FUNC-SYM function's destription."
8842   (let* ((sym (intern (concat (symbol-name (` (, mode-func-sym))) "-help"))))
8843     (`
8844      (defun (, sym) ()
8845        "Mode help."
8846        (interactive)
8847        (with-output-to-temp-buffer "*help*"
8848          (princ (documentation (quote (, mode-func-sym)))))))))
8849
8850 ;;; ----------------------------------------------------------------------
8851 ;;;
8852 (defun ti::macrof-minor-mode-commentary (pfx mode-func-sym)
8853   "Create standard function to print PFX MODE-FUNC-SYM Commentary."
8854   (let* ((name pfx) ;; (symbol-name (` (, mode-func-sym))))
8855          (sym  (intern (concat name "commentary")))
8856          (file1 (substring pfx 0 (1- (length name))))
8857          (file2 (concat file1 ".el")))
8858     (`
8859      (defun (, sym) ()
8860        "Display `finder-commentary'."
8861        (interactive)
8862        ;; Same as what `finde-commentary' uses
8863        ;; One problem: lm-commentary has a bug, which causes killing
8864        ;; the file from emacs after it's done. But we don't want that
8865        ;; if use is viewing or loaded it to emacs before us.
8866        ;;
8867        ;; Work around that bug.
8868        (let ((buffer (or
8869                       (get-buffer (, file2))
8870                       (find-buffer-visiting (, file2))
8871                       (find-buffer-visiting (, file1)))))
8872          (if (not buffer)
8873              (finder-commentary (, file2))
8874            ;;  This is only a pale emulation....will do for now.
8875            (let (str)
8876              (with-current-buffer buffer
8877                (setq str (lm-commentary))
8878                (with-current-buffer (ti::temp-buffer "*Finder*" 'clear)
8879                  (insert str)
8880                  (ti::pmin) (ti::buffer-replace-regexp "^;+" 0 "")
8881                  (ti::pmin) (ti::buffer-replace-regexp "\r" 0 "")
8882                  (display-buffer (current-buffer)))))))))))
8883
8884 ;;; ----------------------------------------------------------------------
8885 ;;;
8886 (defun ti::macrof-minor-mode-viper-attach (pfx mode-func-sym)
8887   "Create standard function PFX MODE-FUNC-SYM to attach mode to viper."
8888   (let* ((name pfx) ;; (symbol-name (` (, mode-func-sym))))
8889          (sym  (intern (concat (symbol-name (` (, mode-func-sym)))
8890                                "-viper-attach")))
8891          (file1 (substring pfx 0 (1- (length name)))))
8892     (`
8893      (defun (, sym) ()
8894        "Attach minor mode to viper with `viper-harness-minor-mode'."
8895        (if (featurep 'viper)
8896            (ti::funcall 'viper-harness-minor-mode (, file1)))))))
8897
8898 ;;; ----------------------------------------------------------------------
8899 ;;;
8900 (defmacro ti::macrof-minor-mode-install
8901   (func-ins-sym
8902    mode-sym
8903    map-sym
8904    prefix-map-sym
8905    mode-name-sym
8906    hook-sym
8907    &rest body)
8908   "Return standard function form.
8909 Returned function will install and remove minor mode.
8910
8911 Input:
8912
8913   FUNC-INS-SYM  symbol, the name of the function that is created.
8914                 E.g. 'xxx-install-mode
8915
8916   MODE-SYM      function symbol to call to run the mode e.g. 'xxx-mode
8917
8918   MAP-SYM       mode's keymap symbol. E.g. 'xxx-mode-map
8919
8920   MODE-NAME-SYM mode's name symbol. E.g. 'xxx-mode-name
8921
8922   HOOK-SYM      hook symbol to call when mode has been installed.
8923                 e.g. 'xxx-key-define-hook, which calls necessary
8924                 functions to install keys and menus.
8925
8926   BODY          Lisp forms executed in the beginning of function.
8927
8928 Created function's arguments:
8929
8930   (&optional remove verb)
8931   REMOVE        uninstall minor mode
8932   VERB          is set for interactive calls: non-nil allows
8933                 displaying messages.
8934
8935 How to call this function:
8936
8937    (ti::macrof-minor-mode-install
8938     xxx-install-mode
8939     xxx-mode
8940     xxx-:mode-map
8941     xxx-:prefix-map-sym
8942     xxx-:mode-name
8943     xxx-:mode-define-keys-hook
8944     (progn
8945      ;; Lisp forms here
8946      nil))
8947
8948 Example how to call created function:
8949
8950   M -x xxx-install-mode      ;; this calls created function and installs mode
8951   (xxx-install-mode)         ;; Same
8952   (xxx-install-mode 'remove) ;; Or prefix ARG, removes the minor mode"
8953   (` (, (ti::macrof-minor-mode-install-1
8954          func-ins-sym
8955          mode-sym
8956          map-sym
8957          prefix-map-sym
8958          mode-name-sym
8959          hook-sym
8960          body))))
8961
8962 ;;; ----------------------------------------------------------------------
8963 ;;;
8964 (defun ti::macrof-minor-mode-install-1
8965   (func-ins-sym
8966    mode-sym
8967    map-sym
8968    prefix-map-sym
8969    mode-name-sym
8970    hook-sym
8971
8972    &rest body)
8973   "Use macro `ti::macrof-minor-mode-install'. See arguments there.
8974 FUNC-INS-SYM MODE-SYM MAP-SYM MODE-NAME-SYM HOOK-SYM BODY"
8975   (let* ((sym (intern (symbol-name (` (, func-ins-sym))))))
8976     (`
8977      (defun (, sym) (&optional remove verb)
8978        "Install or optionally REMOVE minor mode. Calling this always
8979 removes old mode and does reintall."
8980        (interactive "P")
8981        (ti::verb)
8982        (,@ body)
8983        (cond
8984         (remove
8985          (ti::keymap-add-minor-mode '(, mode-sym) nil nil 'remove)
8986          (if verb
8987              (message "minor mode removed")))
8988         (t
8989          (setq (,        map-sym)  (make-sparse-keymap)) ;; always refresh
8990          (setq (, prefix-map-sym)  (make-sparse-keymap)) ;; always refresh
8991          (run-hooks '(, hook-sym))
8992          ;;  Always do reinstall; because keymaps stored permanently and
8993          ;;  making a change later is impossible.
8994          (ti::keymap-add-minor-mode '(, mode-sym) nil nil 'remove)
8995          (ti::keymap-add-minor-mode '(, mode-sym)
8996                                     '(, mode-name-sym)
8997                                     (, map-sym))
8998          (if verb
8999              (message "minor mode installed"))))))))
9000
9001 ;;; ----------------------------------------------------------------------
9002 ;;;
9003 (defmacro ti::macrof-define-keys
9004   (minor--mode-name
9005    minor--mode-desc
9006    func-def-sym
9007    keymap-sym
9008    prefix-keymap-sym
9009    prefix-key-sym
9010    easymenu-sym
9011    easymenu-Name-sym
9012    easymenu-doc-str
9013    easy-menu-forms
9014    eval-body)
9015   "Return standard function form.
9016 The returned function will install keymaps and menu-bar menu for minor mode.
9017
9018 Inside the function you can refer to variables
9019
9020  'root-map'             refers to ROOT keymap from where the prefix map is accessed
9021                         This is the original keymap where the PREFIX-KEY is
9022                         assigned. The actual commands are put to 'map'.
9023  'map'                  refers to separate minor mode prefix keymap
9024  'p'                    holds the prefix key.
9025
9026 Input:
9027
9028  MINOR--MODE-NAME       string
9029  MINOR--MODE-DESC       string
9030  FUNC-DEF-SYM           symbol, function name which is created
9031  KEYMAP-SYM             symbol, keymap where to define keys, must exist
9032  PREFIX-KEY-SYM         symbol, variable holding the prefix key.
9033  [EASYMENU-SYM]         symbol, easy menu variable or nil.
9034  [EASYMENU-NAME-SYM]    symbol, easy menu's menu-bar name variable or nil
9035  [EASYMENU-DOC-STR]     string, Describe string for menu.
9036  [EASY-MENU-FORMS]      forms to define menus
9037  EVAL-BODY              forms executed at the end of function.
9038
9039 Created function's arguments:
9040
9041   ()
9042
9043 How to call this function:
9044
9045    (ti::macrof-define-keys
9046      xxx-mode-define-keys
9047      xxx-:mode-prefix-map
9048      xxx-:mode-prefix-key
9049      xxx-:mode-easymenu
9050      xxx-:mode-easymenu-name
9051      (list
9052        xxx-:mode-easymenu-name
9053        [\"menu item1\"  xxx-function1 t]
9054        [\"menu item2\"  xxx-function2 t]
9055        \"----\"
9056        [\"menu item3\"  xxx-function3 t])
9057      (progn
9058         (define-key  map  \"a\"   'xxx-function1)
9059         (define-key  map  \"b\"   'xxx-function2)
9060         (define-key  map  \"c\"   'xxx-function3)))
9061
9062 Example how to call created function:
9063
9064   (xxx-mode-define-keys)"
9065   (` (, (ti::macrof-define-keys-1
9066          minor--mode-name
9067          minor--mode-desc
9068          func-def-sym
9069          keymap-sym
9070          prefix-keymap-sym
9071          prefix-key-sym
9072          easymenu-sym
9073          easymenu-Name-sym
9074          easymenu-doc-str
9075          easy-menu-forms
9076          eval-body))))
9077
9078 ;;; ----------------------------------------------------------------------
9079 ;;;
9080 (defmacro ti::macrov-mode-line-mode-menu (mode-symbol text)
9081   "Add MODE-SYMBOL to minor mode list in Emacs mode line menu."
9082   (let ((sym  (vector (intern (symbol-name (` (, mode-symbol)))))))
9083     (` (when (boundp 'mode-line-mode-menu) ;; Emacs 21.1
9084          (define-key mode-line-mode-menu (, sym)
9085            '(menu-item (, text)
9086                        (, mode-symbol)
9087                        :button (:toggle . (, mode-symbol))))))))
9088
9089 ;;; ----------------------------------------------------------------------
9090 ;;;
9091 (defun ti::macrof-define-keys-1
9092   (minor--mode-name
9093    minor--mode-desc
9094    func-def-sym
9095    keymap-sym
9096    prefix-keymap-sym
9097    prefix-key-sym
9098    easymenu-sym
9099    easymenu-Name-sym
9100    easymenu-doc-str
9101    easy-menu-forms
9102    body)
9103   "Use macro `ti::macrof-define-keys' and see arguments there.
9104 MODE-NAME FUNC-DEF-SYM KEYMAP-SYM PREFIX-KEYMAP-SYM PREFIX-KEY-SYM
9105 EASYMENU-SYM EASYMENU-NAME-SYM EASYMENU-DOC-STR EASY-MENU-FORMS
9106 BODY"
9107   (let* (sym)
9108     (setq sym (intern (symbol-name (` (, func-def-sym)))))
9109     (`
9110      (defun (, sym) ()
9111        (let* ((root-map  (, keymap-sym))
9112               (map       (, prefix-keymap-sym))
9113               (p         (, prefix-key-sym)))
9114          (when (stringp (, easymenu-doc-str)) ;This could be nil (no menus)
9115            (if (ti::xemacs-p)
9116                (easy-menu-define
9117                  (, easymenu-sym)
9118                  nil
9119                  (, easymenu-doc-str)
9120                  (, easy-menu-forms))
9121              (easy-menu-define
9122                (, easymenu-sym)
9123                (, keymap-sym)
9124                (, easymenu-doc-str)
9125                (, easy-menu-forms))))
9126          ;;  This is no-op, ByteComp silencer.
9127          ;;  ** variable p bound but not referenced
9128          (if (null p)        (setq p nil))
9129          (if (null map)      (setq map nil))
9130          (if (null root-map) (setq root-map nil))
9131          (ti::macrov-mode-line-mode-menu
9132           (, minor--mode-name) (, minor--mode-desc))
9133          ;; (define-key mode-map mode-prefix-key mode-prefix-map)
9134          (when (, prefix-key-sym)
9135            (define-key
9136              (, keymap-sym)
9137              (, prefix-key-sym)
9138              (, prefix-keymap-sym)))
9139          ;;  If you have selected a prefix key that is a natural ABC key;
9140          ;;  then define "aa" as self insert command for "a" character.
9141          ;;
9142          ;;  check also if prefix key defined is like  [{a)]] where "a"
9143          ;;  if a single character. The [{?\C-a)]] is nto accepted as
9144          ;;  repeated key: C-aC-a, only "aa"
9145          (let* ((char (ti::keymap-single-key-definition-p p)))
9146            (when (and (characterp char) (ti::print-p char))
9147              ;;  The prefix key is single; printable character.
9148              (define-key map p 'self-insert-command)))
9149          (, body))))))
9150
9151 ;;; ----------------------------------------------------------------------
9152 ;;;
9153 (defun ti::macrof-version-bug-report-1
9154   (filename
9155    prefix
9156    version-variable
9157    version-value
9158    bug-var-list
9159
9160    &optional
9161    buffer-list
9162    bug-body)
9163   "Use macro `ti::macrof-version-bug-report' and see arguments there.
9164 FILENAME PREFIX VERSION-VARIABLE VERSION-VALUE
9165 BUG-VAR-LIST BUFFER-LIST BUG-BODY."
9166   (let* (sym
9167          ret
9168          elt)
9169     (push 'progn ret)
9170     (setq elt
9171           (list
9172            'defconst (` (, version-variable))
9173            (` (, version-value))
9174            "Package's version information."))
9175     (push elt ret)
9176     (setq sym (intern (format "%s-version" prefix)))
9177     (setq
9178      elt
9179      (`
9180       (defun (, sym) (&optional arg)
9181         "Version information."
9182         (interactive "P")
9183         (ti::package-version-info (, filename) arg))))
9184     (push elt ret)
9185     (setq sym (intern (format "%s-submit-bug-report" prefix)))
9186     (setq
9187      elt
9188      (`
9189       (defun (, sym) ()
9190         "Send bug report or feedback."
9191         (interactive)
9192         (ti::package-submit-bug-report
9193          (, filename)
9194          (, version-variable)
9195          (, bug-var-list)
9196          'verbose
9197          (, buffer-list))
9198         (, bug-body))))
9199     (push elt ret)
9200     (nreverse ret)))
9201
9202 ;;; ----------------------------------------------------------------------
9203 ;;;
9204 (defmacro ti::macrof-version-bug-report
9205   (filename
9206    prefix
9207    version-variable
9208    version-value
9209    bug-var-list
9210    &optional
9211    buffer-list
9212    bug-body)
9213   "Return standard function form.
9214 One variable and two functions are created.
9215
9216 Input:
9217
9218   FILENAME          string e.g. xxx.el
9219   PREFIX            package prefix for functions e.g. xxx
9220   VERSION-VARIABLE  symbol variable holding the version information.
9221   VERSION-VALUE     value for the variable. Should be RCS Id string or the
9222                     like.
9223   BUG-VAR-LIST      variable list to send with bug report
9224   BUG-BODY          Lisp forms for the bug function.
9225
9226 How to call this macro:
9227
9228     (ti::macrof-version-bug-report
9229      \"xxx.el\"
9230      \"xxx\"
9231      xxx-:version-id
9232      \"...version Id string here, RCS controlled.\"
9233
9234      '(xxx-:load-hook
9235        xxx-:mode-hook
9236        xxx-mode-define-keys-hook
9237        xxx-:mode-name))
9238
9239 Example how to call created functions:
9240
9241   M - x xxx-submit-bug-report
9242   M - x xxx-version"
9243   (`(, (ti::macrof-version-bug-report-1
9244         filename
9245         prefix
9246         version-variable
9247         version-value
9248         bug-var-list
9249         buffer-list
9250         bug-body))))
9251
9252 ;;; ----------------------------------------------------------------------
9253 ;;;
9254 (defun ti::macrof-debug-1
9255   (prefix
9256    debug-function
9257    debug-toggle-function
9258    debug-buffer-show-function
9259    debug-variable
9260    debug-buffer)
9261   "Use macro `ti::macrof-debug' and see argument there.
9262 PREFIX
9263 DEBUG-FUNCTION DEBUG-TOGGLE-FUNCTION DEBUG-BUFFER-SHOW-FUNCTION
9264 DEBUG-VARIABLE DEBUG-BUFFER."
9265   (let* (str
9266          ret
9267          elt)
9268     (push 'progn ret)
9269
9270     (setq elt
9271           (list
9272            'defvar (` (, debug-variable))
9273            nil
9274            "Debug control: on or off."))
9275     (push elt ret)
9276
9277     (setq elt
9278           (list
9279            'defvar (` (, debug-buffer))
9280            (format "*%s-debug*" prefix)
9281            "Debug output buffer."))
9282     (push elt ret)
9283     (setq str
9284           (concat
9285            "Generate debug\n"
9286            "Prefix ARG: nil = toggle, 0 = off, 1 = on."))
9287     (setq
9288      elt
9289      (`
9290       (defun (, debug-toggle-function) (&optional arg)
9291         (, str)
9292         (interactive "P")
9293         (let* ((buffer (get-buffer (, debug-buffer))))
9294           (ti::bool-toggle (, debug-variable) arg)
9295           (when (and (, debug-variable)
9296                      buffer
9297                      (y-or-n-p "Clear debug buffer?"))
9298             (ti::erase-buffer buffer))
9299           (if (interactive-p)
9300               (message "Debug is %s"
9301                        (if (, debug-variable)
9302                            "on"
9303                          "off")))))))
9304     (push elt ret)
9305     (when debug-buffer-show-function
9306       (setq str "Show debug buffer.")
9307       (setq
9308        elt
9309        (`
9310         (defun (, debug-buffer-show-function) (&optional arg)
9311           (, str)
9312           (interactive "P")
9313           (let* ((buffer (get-buffer (, debug-buffer))))
9314             (ti::bool-toggle (, debug-variable) arg)
9315             (if (null buffer)
9316                 (message "There is no debug buffer to show.")
9317               (display-buffer buffer))))))
9318       (push elt ret))
9319     (setq str
9320           (concat "Write debug log to " ;; (` (, debug-buffer ))
9321                   " if "
9322 ;;;                (symbol-name (quote (` (, debug-variable)) ))
9323                   "is non-nil."))
9324
9325     ;; We are returning a macro in next elt.
9326     (setq
9327      elt
9328      (`
9329       (defmacro (, debug-function) (&rest args)
9330 ;;;      (when (, debug-variable)
9331 ;;;        (let* ((ti:m-debug-buffer (, debug-buffer )))
9332         (when (, debug-variable)
9333           (with-current-buffer (get-buffer-create (, debug-buffer))
9334             (goto-char (point-max))
9335             (while args
9336               (insert (format "|%s" (eval (pop args)))))
9337             (insert "\n"))))))
9338     (push elt ret)
9339     (nreverse ret)))
9340
9341 ;;; ----------------------------------------------------------------------
9342 ;;;
9343 (defmacro ti::macrof-debug-lowlevel
9344   (prefix
9345    debug-function
9346    debug-toggle-function
9347    debug-buffer-show-function
9348    debug-variable
9349    debug-buffer)
9350   "Return standard function forms for debug interface.
9351 One variable, one function and one macro will be created.
9352
9353 Input:
9354
9355   PREFIX                     string, symbols' prefix.
9356   DEBUG-FUNCTION             symbol, function name to generate debug
9357   DEBUG-TOGGLE-FUNCTION      symbol, function name to turn on/off debug
9358   DEBUG-BUFFER-SHOW-FUNCTION symbol, fucntion to display debug buffer.
9359   DEBUG-VARIABLE             symbol, variable to control debug
9360   DEBUG-BUFFER               string, buffer name where to write debug.
9361
9362 How to call this macro:
9363
9364   (ti::macrof-debug xxx-debug xxx-debug-toggle xxx-debug-show
9365    xxx-debug \"*xxx-debug*\")
9366
9367 Example how to call created functions:
9368
9369   M - x xxx-debug-show
9370
9371   M - x xxx-debug-toggle  ;; To turn on or off debug package debug
9372   (xxx-debug-toggle 0)    ;; off
9373   (xxx-debug-toggle 1)    ;; on
9374
9375   ;;  To generate debug from inside code, you call:
9376   (xxx-debug ... anything frame-pointer buffer-pointer ...)"
9377   (`(, (ti::macrof-debug-1
9378         prefix
9379         debug-function
9380         debug-toggle-function
9381         debug-buffer-show-function
9382         debug-variable
9383         debug-buffer))))
9384
9385 ;;; ----------------------------------------------------------------------
9386 ;;;
9387 (defmacro ti::macrof-debug-standard (prefix &optional var-prefix)
9388   "Make standard debug interface according to PREFIX and VAR-PREFIX."
9389   (let* ((d-func   (intern (format "%s-debug" prefix)))
9390          (dt-func  (intern (format "%s-debug-toggle" prefix)))
9391          (ds-func  (intern (format "%s-debug-show" prefix)))
9392          (pfx      (or var-prefix "-"))
9393          (d-var    (intern (format "%s%sdebug" prefix pfx)))
9394          (d-buffer (intern (format "%s%sdebug-buffer" prefix pfx))))
9395     (`(, (ti::macrof-debug-1
9396           prefix
9397           d-func
9398           dt-func
9399           ds-func
9400           d-var
9401           d-buffer)))))
9402
9403 ;;; ----------------------------------------------------------------------
9404 ;;;
9405 (defun ti::macrof-install-pgp-tar-1
9406   (func-ins-sym elisp-file &optional log-buffer)
9407   "Use macro `ti::macrof-install-pgp-tar' and see arguments there.
9408 FUNC-INS-SYM ELISP-FILE LOG-BUFFER."
9409   (let* (sym)
9410
9411     (setq sym (intern (symbol-name (` (, func-ins-sym)))))
9412
9413     (`
9414      (defun (, sym) (dir)
9415        "Install additional programs from the end of package."
9416        (interactive "DSave programs to directory: ")
9417        (let* ((file    (, elisp-file))
9418               (source  (or (locate-library file)
9419                            (error "can't find %s along load-path." file))))
9420          (ti::package-install-pgp-tar
9421           dir
9422           (or (, log-buffer)
9423               "*install-log*")
9424           source))))))
9425
9426 ;;; ----------------------------------------------------------------------
9427 ;;;
9428 (defmacro ti::macrof-install-pgp-tar
9429   (func-ins-sym elisp-file &optional log-buffer)
9430   "Return standard pgp tar install function.
9431 It handles installing pgp base 64 signed tar block from the end of file.
9432
9433   1.   Create tar file (it sould not have directory names, but ...)
9434   2.   pgp base64 sign the tar file (clearsig off)
9435   3.   paste pgp data to to end of your lisp package
9436
9437   ;; -----BEGIN PGP MESSAGE-----
9438   ;; Version: 2.6.3ia
9439   ;;
9440   ;; owHsWc1vG0l2n0GwwYjA3pJLgEXKlNaSDJLilySblrWWLXrMrCQrpOydzcxA02wW
9441   ...
9442   ;; -----END PGP MESSAGE-----
9443
9444 And nothing more is needed to get your programs untarred nicely.
9445 The drop directory is asked from the user when he calls this function.
9446
9447 Input:
9448
9449   FUNC-INS-SYM  symbol, the created install function name
9450   ELISP-FILE    your Lisp package name (with .el)
9451   [LOG-BUFFER]  where to print the install log. Can be nil.
9452
9453 Created function's arguments:
9454
9455   (dir)
9456   DIR           Where to untar the included file, asked interactively
9457
9458 How to call this function:
9459
9460   ;;;###autoload (autoload 'xxx-install-programs  \"package-file\" t t)
9461
9462    (ti::macrof-install-pgp-tar
9463     xxx-install-programs
9464    \"xxx.el\"
9465    \"*xxx-install-log*\"))
9466
9467 Example how to call created function:
9468
9469   M - x xxx-install-programs"
9470   (` (, (ti::macrof-install-pgp-tar-1
9471          func-ins-sym
9472          elisp-file
9473          log-buffer))))
9474
9475 ;;; ----------------------------------------------------------------------
9476 ;;;
9477 (defmacro ti::macrof-minor-mode-wizard
9478   (pfx                                  ;1
9479    mode-Name                            ;
9480    mode-Name-prefix-key                 ;
9481    easymenu-Name                        ;
9482    custom-group                         ;
9483    variable-style                       ;6
9484
9485    doc-str                              ;7
9486    mode-desc                            ;
9487    minor-mode-body                      ;
9488
9489    easymenu-doc                         ;10
9490    easymenu-body                        ;
9491    define-key-body)                     ;12
9492   "Do all the necessary things to create minor mode.
9493 Following macros are called one by one. If you want personalized
9494 minor mode control, call each of these individually and don't use
9495 this wizard.
9496
9497     `ti::macrov-minor-mode'
9498     `ti::macrof-minor-mode-install'
9499     `ti::macrof-minor-mode'
9500     `ti::macrof-minor-mode-on'
9501     `ti::macrof-minor-mode-off'
9502     `ti::macrof-minor-mode-help'
9503     `ti::macrof-define-keys'
9504
9505 Input:
9506
9507     PFX                     See -vmacro-
9508     MODE-NAME               See -vmacro-
9509     MODE-NAME-PREFIX-KEY    See -vmacro-
9510     EASYMENU-NAME           See -vmacro-
9511     CUSTOM-GROUP            See -vmacro-
9512     VARIABLE-STYLE          See -vmacro-
9513
9514     DOC-STR                 See -fmacro-minor-mode
9515     MODE-DESC               See -fmacro-minor-mode
9516     MINOR-MODE-BODY         See -fmacro-minor-mode must be in format ((BOBY))
9517
9518     EASYMENU-DOC            See -fmacro-define-keys must be in format ((BOBY))
9519     EASYMENU-BODY           See -fmacro-define-keys must be in format ((BOBY))
9520     DEFINE-KEY-BODY         See -fmacro-define-keys
9521
9522 How to call this function:
9523
9524    See example tinylisp.el package which uses this function to create
9525    minor mode.
9526
9527 If you want to see what this macro produces, use
9528
9529   (macroexpand '(ti::macrof-minor-mode-wizard ...))C - x C - e
9530
9531 Here is example how you would define the minor mode.
9532
9533   (eval-and-compile   ;; So that defvars and defuns are seen
9534     (ti::macrof-minor-mode-wizard
9535      \"xxx-\"               ;; prefix for variables and functions
9536      \" xxxModeline\"       ;; Modeline name
9537      \"\\C-cx\"              ;; prefix key for mode.
9538      \"xxxMenubar\"         ;; Menu bar name
9539      nil                  ;; <forget this>
9540
9541      \"XXX minor mode. Does fancy things.\"  ;; mode description
9542
9543      \"XXX help\"    ;; message displayed when user calls mode
9544
9545      ;; ............................................................
9546      (progn
9547       ;; Run body-of-code when minor mode is called
9548       nil)
9549
9550      ;; ............................................................
9551      ;; Next id used by easy-menu.el and defines menu items.
9552      (list
9553       xxx-mode-easymenu-name
9554       [\"Eval whole buffer\" xxx-eval-current-buffer    t]
9555       ..)
9556
9557      ;; ............................................................
9558      ;;  this block defines keys to the mode. The mode minor map is
9559      ;;  locally bound to 'map' symbol.
9560      (progn
9561        (define-key map \"-\" 'xxx-eval-current-buffer)
9562        (define-key map \"=\" 'xxx-calculate))))
9563 "
9564   (` (,
9565       (ti::macrof-minor-mode-wizard-1
9566        pfx                              ;1
9567        mode-Name                        ;
9568        mode-Name-prefix-key             ;
9569        easymenu-Name                    ;
9570        custom-group                     ;
9571        variable-style                   ;6
9572
9573        doc-str                          ;7
9574        mode-desc                        ;
9575        minor-mode-body                  ;9
9576
9577        easymenu-doc                     ;10
9578        easymenu-body                    ;
9579        define-key-body))))              ;12
9580
9581 ;;; ----------------------------------------------------------------------
9582 ;;;
9583 (defun ti::macrof-minor-mode-wizard-1
9584   (pfx                                  ;1
9585    mode-Name                            ;2
9586    mode-Name-prefix-key                 ;3
9587    easymenu-Name                        ;4
9588    custom-group                         ;5
9589    variable-style                       ;6
9590
9591    doc-str                              ;7
9592    mode-desc                            ;8
9593    minor-mode-body                      ;9
9594
9595    easymenu-doc                         ;10
9596    easymenu-body                        ;11
9597    define-key-body)                     ;12
9598   "Use macro `ti::macrof-minor-mode-wizard' and see parameters there.
9599    PFX
9600    MODE-NAME
9601    MODE-NAME-PREFIX-KEY
9602    EASYMENU-NAME
9603    CUSTOM-GROUP
9604    VARIABLE-STYLE
9605    DOC-STR
9606    MODE-DESC
9607    MINOR-MODE-BODY
9608
9609    EASYMENU-DOC
9610    EASYMENU-BODY
9611    DEFINE-KEY-BODY"
9612
9613   (let* (sym1
9614          sym2
9615          sym3
9616          sym4
9617          sym5
9618          sym6
9619          sym7
9620          ret
9621          elt
9622          vs)
9623     (ti::nconc ret 'eval-and-compile)
9624     ;; ........................................... create variables ...
9625     (setq elt
9626           (ti::macrov-minor-mode-1
9627            pfx
9628            mode-Name
9629            mode-Name-prefix-key
9630            easymenu-Name
9631            custom-group
9632            variable-style))
9633     (setq vs  (if variable-style
9634                   variable-style
9635                 pfx))
9636 ;;;    (ti::d!! "\n\n>>" elt)
9637     (ti::nconc ret elt)
9638     ;; .................................... create install function ...
9639     (setq sym1 (intern (concat pfx "install-mode"))
9640           sym2 (intern (concat pfx "mode"))
9641           sym3 (intern (concat vs "mode-map"))
9642           sym4 (intern (concat vs "mode-prefix-map"))
9643           sym5 (intern (concat vs "mode-name"))
9644           sym6 (intern (concat vs "mode-define-keys-hook")))
9645 ;;;    (ti::d!! "\n\n>>minor-mode-install" sym1 sym2 sym3 sym4 sym5  "\n")
9646     (setq elt (ti::macrof-minor-mode-install-1
9647                sym1 sym2 sym3 sym4 sym5 sym6))
9648     (ti::nconc ret elt)
9649     ;; ....................................... define keys function ...
9650     (setq sym1 (intern (concat pfx "mode-define-keys"))
9651           sym2 (intern (concat vs  "mode-map"))
9652           sym3 (intern (concat vs  "mode-prefix-map"))
9653           sym4 (intern (concat vs  "mode-prefix-key"))
9654           sym5 (intern (concat vs  "mode-easymenu"))
9655           sym6 (intern (concat vs  "mode-easymenu-name"))
9656           sym7 (intern (concat pfx "mode")))
9657 ;;;   (ti::d!! "\n\n>>define-keys"  sym1 sym2 sym3 sym4 sym5)
9658     (setq elt
9659           (ti::macrof-define-keys-1
9660            sym7
9661            mode-desc
9662            sym1
9663            sym2
9664            sym3
9665            sym4
9666            sym5
9667            sym6
9668            easymenu-doc
9669            easymenu-body
9670            define-key-body))
9671     (ti::nconc ret elt)
9672     ;; ................................. create minor mode function ...
9673     (setq sym1 (intern (concat pfx "mode"))
9674           sym2 (intern (concat pfx "install-mode"))
9675           sym3 (intern (concat pfx "mode"))
9676           sym4 (intern (concat vs  "mode-name"))
9677           sym5 (intern (concat vs  "mode-prefix-key"))
9678           sym6 (intern (concat vs  "mode-easymenu"))
9679           sym7 (intern (concat vs  "mode-hook")))
9680 ;;;   (ti::d!! "\n\n>>minor-mode" sym1 sym2 sym3 sym4 sym5 sym6 sym7 "\n")
9681     (setq elt
9682           (ti::macrof-minor-mode-1
9683            sym1 doc-str  sym2
9684            sym3 sym4     sym5
9685            sym6 nil      mode-desc
9686            sym7
9687            minor-mode-body))
9688     (ti::nconc ret elt)
9689     (setq elt (ti::macrof-minor-mode-on   sym1))
9690     (ti::nconc ret elt)
9691     (setq elt (ti::macrof-minor-mode-off  sym1))
9692     (ti::nconc ret elt)
9693     (setq elt (ti::macrof-minor-mode-help sym1))
9694     (ti::nconc ret elt)
9695     (setq elt (ti::macrof-minor-mode-commentary pfx sym1))
9696     (ti::nconc ret elt)
9697     (setq elt (ti::macrof-minor-mode-viper-attach pfx sym1))
9698     (ti::nconc ret elt)
9699     ret))
9700
9701 ;;}}}
9702
9703 (provide   'tinylib)
9704
9705 ;;; tinylib.el ends here