]> git.donarmstrong.com Git - lib.git/blob - emacs_el/tiny-tools/tiny/tinydired.el
add tiny-tools
[lib.git] / emacs_el / tiny-tools / tiny / tinydired.el
1 ;;; tinydired.el --- Dired enchancements. Backgroud Ange ftp support
2
3 ;; This file is not part of Emacs
4
5 ;;{{{ Id
6
7 ;; Copyright (C)    1996-2007 Jari Aalto
8 ;; Keywords:        extensions
9 ;; Author:          Jari Aalto
10 ;; Maintainer:      Jari Aalto
11 ;;
12 ;; To get information on this program use ident(1) or call M-x
13 ;; tinydired-version. Look at code with folding.el
14
15 ;; COPYRIGHT NOTICE
16 ;;
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
20 ;; any later version.
21 ;;
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
25 ;; for more details.
26 ;;
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
31 ;;
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
33
34 ;;}}}
35 ;;{{{ Install
36
37 ;; ....................................................... &t-install ...
38 ;;  Put this file on your Emacs-Lisp load path, add following into your
39 ;;  ~/.emacs startup file.
40 ;;
41 ;;      ;;  Make sure the keys will be defined.
42 ;;      (setq tinydired-:force-add-keys-flag 'override)
43 ;;
44 ;;      (autoload 'tinydired-hook-control                   "tinydired" "" t)
45 ;;      (autoload 'tinydired-switch-to-some-ange-ftp-buffer "tinydired" "" t)
46 ;;      (add-hook 'tinydired-:load-hook                 'tinydired-hook-control)
47 ;;      (add-hook 'dired-mode-hook '(lambda () (require 'tinydired) nil))
48 ;;
49 ;;  For more personal setup, you have to look at the calls in function
50 ;;  `tinydired-hook-control' and put your own initializations into
51 ;;  `dired-mode-hook' and `dired-after-readin-hook'.
52 ;;
53 ;;  To select/kill ange buffers, use these bindings
54 ;;
55 ;;      (global-set-key "\C-cab" 'tinydired-switch-to-some-ange-ftp-buffer)
56 ;;      (global-set-key "\C-cak" 'tinydired-kill-all-ange-buffers)
57 ;;      (global-set-key "\C-caK" 'tinydired-kill-all-ange-and-dired-buffers)
58 ;;
59 ;;  If you don't want default keybindings, modify variable
60 ;;
61 ;;      tinydired-:bind-hook
62 ;;
63 ;;  Help about keys - do this in dired buffer after you've loaded this file
64 ;;
65 ;;      t C-h           enchanced "tiny" dired commands
66 ;;      a C-h           enchanced "ange-ftp" commands
67 ;;
68 ;;  If you have any questions, always use function
69 ;;
70 ;;      M-x tinydired-submit-bug-report
71
72 ;;}}}
73 ;;{{{ Documentation
74 ;; ..................................................... &t-commentary ...
75
76 ;;; Commentary:
77
78 ;;  Preface, Jan 1996
79 ;;
80
81 ;;      This package started evolving, when there was need for something
82 ;;      more from ange-ftp, like background file loading. Ange-ftp also had
83 ;;      nasty habbit of putting user to just downloaded .zip or .tar.gz
84 ;;      buffer. That not what was usually wanted, but to download the files
85 ;;      somewhere other than inside emacs. There was need for ability to
86 ;;      *mark* files for download and get them all at once to a download
87 ;;      directory. With standard `ange-ftp' you would have to load them one
88 ;;      by one. Sometimes you may want to go associated `ange-ftp' buffer
89 ;;      and give commands directly there, so a command to switch between
90 ;;      ange-ftp and dired buffers would be handy.
91 ;;
92 ;;      Now you can do this with standard `ange-ftp' and Emacs dired.
93 ;;
94 ;;      Note: This paskage is just extension to `ange-ftp', consider
95 ;;      getting next generation ange-ftp, the `EFS', if you want
96 ;;      overall better and more complete interface. Use this package if
97 ;;      you only need features like batch put/get at backround.
98 ;;      (Further note: EFS was later installed to XEmacs and it does not work
99 ;;      any more with Emacs.)
100 ;;
101 ;;  Overview of features
102 ;;
103 ;;      o   Few enchancements to dired mode. Eg. keep only one
104 ;;          dired buffer when ascending to directory. Shorten symlinks.
105 ;;      o   User can mark and put files into STORE and start a backgroud
106 ;;          ange-ftp session to get STORED files into download directory
107 ;;      o   Easy switching between ange-ftp session buffer and dired buffer
108 ;;      o   Dealing with ange ftp buffers in general
109 ;;          (x)  killing all ange buffers at once
110 ;;          (x)  killing all ange + dired ange buffers at once.
111 ;;          (x)  switching to ange buffers with completion
112 ;;      o   Run "!" on ange ftp dired buffer (operate on local copy)
113 ;;      o   customizable backup file flagging.
114 ;;      o   other handy dired commands, like "pop to this file in emacs."
115 ;;          "find all marked files"...
116 ;;
117 ;;  XEmacs note
118 ;;
119 ;;      The dired and ange-ftp implementation (nowadays efs) is
120 ;;      completely differen than in Emacs
121 ;;
122 ;;      ** THIS PACKAGE IS FOR Emacs ONLY **
123 ;;
124 ;;  General dired additions
125 ;;
126 ;;      In simplest form. This module installs some functions in your
127 ;;      dired hooks. Their purpose is
128 ;;
129 ;;      o   To keep your dired buffer sorted so that directories are
130 ;;          always put first.
131 ;;      o   Delete unwanted files from dired buffer automatically.
132 ;;      o   Shorten the symlink references, so that they don't spread
133 ;;          multiple lines and ruin your view.
134 ;;
135 ;;      It also changes one dired function with `defadvice', so that you
136 ;;      can control if you want to have only one dired buffer when
137 ;;      ascending to another directory. See variable:
138 ;;
139 ;;          tinydired-:use-only-one-buffer-flag
140 ;;
141 ;;  Dired and ange-ftp additions
142 ;;
143 ;;      When you want to start ftp session in emacs you just do
144 ;;
145 ;;          C-x C-f /login@site:/dir/dir/file
146 ;;
147 ;;      Let's take an example: To see what new things has arrived
148 ;;      to GNU site, you'd do this:
149 ;;
150 ;;          C-x C-f /ftp@prep.ai.mit.edu:/pub/gnu/
151 ;;
152 ;;      After that you are put into the dired listing, where you
153 ;;      can mark files with dired-mark command
154 ;;
155 ;;          m           Mark file
156 ;;
157 ;;      Now you have files ready. Next put files into batch STORAGE.
158 ;;      There is "a" prefix for ange-ftp related commands.
159 ;;
160 ;;          a S         Big S put selected files into storage
161 ;;          a q         To check what files you have batched
162 ;;          a c         To clear the batch storage
163 ;;
164 ;;      Now start ftp'ding the files in background. You're prompted
165 ;;      for the download directory.
166 ;;
167 ;;          a g         Get marked file(s)
168 ;;
169 ;;      If you want to operate on the associated ftp buffer
170 ;;      directly, there is command
171 ;;
172 ;;          a b         For "buffer change"
173 ;;
174 ;;      that puts you into ftp, where the dired buffer refers. When
175 ;;      you're in the ftp buffer you have some keybinding available.
176 ;;
177 ;;          C-c f       insert stored files on the line
178 ;;          C-c d       insert directory name
179 ;;          C-c b       back to dired window
180 ;;
181 ;;      It's sometimes handy that you can give direct ftp commands.
182 ;;
183 ;;  Setting up ange ftp
184 ;;
185 ;;      Here is my settings, which you can use as a reference so that you
186 ;;      get the ange running. For more details, see the ange-ftp.el's
187 ;;      source code. These settings include firewall "ftpgw.example.com"
188 ;;
189 ;;          ;; (setq ange-ftp-generate-anonymous-password t)
190 ;;          (setq ange-ftp-dumb-unix-host-regexp  "tntpc") ;PC hosts
191 ;;          (setq ange-ftp-gateway-host "ftpgw.example.com")
192 ;;          (setq ange-ftp-smart-gateway t)
193 ;;          (setq ange-ftp-local-host-regexp "\\.myhost\\.\\(com|fi\\)|^[^.]*$")
194 ;;          ;;  Always use binary
195 ;;          (setq ange-ftp-binary-file-name-regexp ".")
196 ;;          (autoload 'ange-ftp-set-passwd "ange-ftp" t t)
197 ;;          (setq ange-ftp-generate-anonymous-password "jdoe@example.com")
198 ;;
199 ;;  How to use this module 3 -- special vc
200 ;;
201 ;;      There are some extra commands that you may take a look at.
202 ;;      See source code of bind function
203 ;;
204 ;;          tinydired-default-other-bindings
205 ;;
206 ;;      What additional commands you get when loading this module.
207 ;;
208 ;;      The VC special commands were programmed, because I felt that the
209 ;;      C-x v v in dired mode didn't quite do what I wanted. I wanted
210 ;;      simple ci/co/revert commands for files that were in VC control.
211 ;;      And I wanted to handle them individually, expecially when ci'ing.
212 ;;      (written for Emacs 19.28).
213 ;;
214 ;;      This VC part of the package is highly experimental.
215 ;;      I'm not sure if I support it in further releases.
216 ;;
217 ;;  Important ange-ftp interface note
218 ;;
219 ;;      The ange ftp batch interface used here may cause unpredictable
220 ;;      problems. Sometimes the `get' or `put' process doesn't start at all
221 ;;      although you see message saying it started the job. I have had
222 ;;      several occurrances where `lcd' cmd succeeded, but then nothing
223 ;;      happened. Repeating the `put' or `get' command cleared the problem
224 ;;      whatever it was.
225 ;;
226 ;;      So, never trust the message `completed', unless you saw that the
227 ;;      download percentage count started running. If you're downloading
228 ;;      important file, double check the real ftp buffer for correct response.
229 ;;      Try again if ftp wasn't started. Another way to clear the problem: kill
230 ;;      the ange ftp buffer and try the command from dired again. It
231 ;;      automatically opens session to the site.
232 ;;
233 ;; Advertise -- other useful packages
234 ;;
235 ;;      There are exellent dired extensions around, please consider getting
236 ;;      these packages:
237 ;;
238 ;;      o   dired-sort.el (requires date-parse.el)
239 ;;      o   dired-tar.el
240 ;;
241 ;;  Note: Slow autoload
242 ;;
243 ;;      When you have added the autoloads into your .emacs, the first time
244 ;;      you bring up dired buffer may be quite slow. This is normal, Emacs
245 ;;      just need to load some additional files that this package uses.
246 ;;
247 ;;  Note: Refreshing the view takes long time / point isn't exatly the same
248 ;;
249 ;;      This is normal, dired is just slow and program has to do lot of
250 ;;      work to maintain the "view". Eg. save view, save marks, delete
251 ;;      marks, revert, sort, restore marks... Only the current line
252 ;;      position is preserved where user was, not point.
253 ;;
254 ;;  Note: Code
255 ;;
256 ;;      Emacs ships with package `dired-x.el', which seems to offer some
257 ;;      more goodies to dired. Currently, if the `dired-x' is detected the
258 ;;      appropriate functions in this package are diabled, to prevent
259 ;;      overlapping behavior. However, if the function behaves differently
260 ;;      than the one in some dired extension package, then the function
261 ;;      isn't disabled. Eg. see `tinydired-load-all-marked-files', which can turn
262 ;;      off marks.
263
264 ;;}}}
265
266 ;;; Change Log:
267
268 ;;; Code:
269
270 ;;{{{ setup: require
271
272 ;;; ......................................................... &require ...
273
274 (require 'backquote)
275 (require 'dired)
276 (require 'advice)
277
278 (require 'tinylibm)
279
280 (eval-and-compile
281   (autoload 'dired-do-shell-command "dired-x" "" t)
282
283   ;; We really don't need to load full packages, so use these..
284   (defvar   vc-dired-mode)
285   (autoload 'vc-dired-mode                      "vc")
286   (autoload 'vc-finish-logentry                 "vc")
287   (autoload 'vc-next-action-on-file             "vc")
288   (autoload 'vc-workfile-unchanged-p            "vc")
289
290   ;; Too bad that can't autoload this one...
291   (defvar    vc-dired-mode                      nil)
292   (autoload 'vc-registered                      "vc-hooks")
293
294   ;; The ange interface in this package is based on Emacs only
295   (if (ti::emacs-p)
296       (autoload 'ange-ftp-ftp-name              "ange-ftp"))
297
298   (autoload 'dired-bunch-files                  "dired-aux")
299   (autoload 'dired-run-shell-command            "dired-aux")
300   (autoload 'dired-shell-stuff-it               "dired-aux")
301
302   (defvar   diff-switches) ;; in diff.el
303   (autoload 'ediff-files                        "ediff" "" t))
304
305 (eval-when-compile
306   (ti::package-use-dynamic-compilation)
307   (when (ti::xemacs-p)
308     (message "\n\
309   ** tinydired.el: This package is for Emacs only.\n\
310                    Dired and ange-ftp interfaces are incompatible between\n\
311                    Emacs and XEmacs.
312                    If you see XEmacs byte compiler error:
313                       evaluating (< nil 0): (wrong-type-argument..
314                    you can ignore it safely. The problem is in
315                    dired.el::dired-map-over-marks"))
316
317   (unless (boundp 'dired-move-to-filename-regexp)
318     (message "\
319   ** tinydired.el: Error, this Emacs did not define dired-move-to-filename-regexp"))
320   (defvar dired-move-to-filename-regexp))
321
322 (ti::package-defgroup-tiny TinyDired tinydired-: extensions
323   "Dired enchancements. Backgroud Ange ftp support.
324   Overview of features
325
326         o   Few enchancements to dired mode. Eg. keep only one
327             dired buffer when ascending to directory. Shorten symlinks.
328         o   User can mark and put files into STORE and start a backgroud
329             ange-ftp session to get STORED files into download directory
330         o   Easy switching between ange-ftp session buffer and dired buffer
331         o   Dealing with ange ftp buffers in general
332             (x)  killing all ange buffers at once
333             (x)  killing all ange + dired ange buffers at once.
334             (x)  switching to ange buffers with completion
335         o   Run ! on ange ftp dired buffer (operate on local copy)
336         o   customizable backup file flagging.
337         o   other handy dired commands, like: pop to this file in emacs.
338             find all marked files ...")
339
340 ;;}}}
341 ;;{{{ setup: vars
342
343 ;;; .......................................................... &v-bind ...
344 ;;; handle extra keybindings.
345
346 (defcustom tinydired-:bind-hook
347   '(tinydired-default-ange-bindings
348     tinydired-default-other-bindings)
349   "*Single function or list of functions to bind keys.
350 These are installed to `dired-mode-hook' automatically when this package
351 is loaded."
352   :type  'hook
353   :group 'Tinydired)
354
355 ;;; ......................................................... &v-hooks ...
356
357 (defcustom tinydired-:readin-hook
358   (delq nil
359         (list
360          (cond
361           ((ti::emacs-p "21")
362            ;; Includes variable ls-lisp-dirs-first
363            (message "TinyDired: `ls-lisp-dirs-first' set to t, DO NOT CHANGE.")
364            (setq ls-lisp-dirs-first t)
365            nil)
366           (t
367            'tinydired-sort-dir))
368          'tinydired-kill-files
369          'tinydired-shorten-links))
370   "*List of functions to run after dired read.
371 These are inserted into `dired-after-readin-hook' when package
372 installs itself. Do not remove 'tinydired-sort-dir' or
373 it paralyzes package, because it relies on seeing directories first
374 in the dired listing."
375   :type  'hook
376   :group 'Tinydired)
377
378 (defcustom tinydired-:load-hook nil
379   "*Hook run when package has been loaded."
380   :type  'hook
381   :group 'Tinydired)
382
383 ;;; ....................................................... &v-private ...
384
385 (defvar tinydired-:file-store nil
386   "Private. Storage of filenames.")
387 (make-variable-buffer-local 'tinydired-:file-store)
388
389 (defvar tinydired-:directory nil
390   "Private. Directory name.")
391
392 (defvar tinydired-:mark-list nil
393   "Private. Saved filename mark list.")
394
395 (defvar tinydired-:mput-last-ftp nil
396   "Private. Last ftp mput site string.")
397 (make-variable-buffer-local 'tinydired-:mput-last-ftp)
398
399 (defvar tinydired-:mput-history nil
400   "Private. History variable.")
401
402 (defvar tinydired-:previous-buffer nil
403   "Private. Recorded buffer, before leaping in another.")
404
405 (defvar tinydired-:dir-copy-buffer "*tinydired-dir*"
406   "Private. Copy of current directory. Created every time when needed.")
407
408 (defvar tinydired-:dired-directory-ange-regexp "[@:]"
409   "Regexp to match `dired-directory' to find ange-ftp buffers.")
410
411 ;;}}}
412 ;;{{{ setup: User vars
413
414 ;;; ........................................................ &v-public ...
415 ;;; User configurable
416
417 (defcustom tinydired-:tmp-dir
418   (dolist (dir '("~/tmp/dired/"
419                  "~/tmp"
420                  "~"))
421     (when (file-directory-p dir)
422       (return dir)))
423   "*Temporary directory where to store ange ftp files.
424 This should be user's private directory, and _must_not_ not be
425 /tmp,  because someone else may be running tinydired too and using
426 same filenames."
427   :type  'directory
428   :group 'Tinydired)
429
430 (defcustom tinydired-:download-dir
431   (dolist (dir '("~/tmp/ftp"
432                  "~/ftp"
433                  "~/tmp"
434                  "~"))
435     (when (file-directory-p dir)
436       (return dir)))
437   "*Directory where to down load selected files in dired listing."
438   :type  'directory
439   :group 'Tinydired)
440
441 (defcustom tinydired-:force-add-keys-flag 'overrride
442   "*Non-nil means to install and override default keys to dired.
443 Normally the keys are defined _only_ if the prefix key is in state
444 'undefined"
445   :type  'boolean
446   :group 'Tinydired)
447
448 (defcustom tinydired-:use-only-one-buffer-flag t
449   "*Non-nil means the previous dired buffer is killed when ascending to next.
450 This makes sure you have only one dired buffer for each dired session.
451 This feature is not used if dired-x is present."
452   :type 'boolean
453   :group 'Tinydired)
454
455 (defcustom tinydired-:page-step 10
456   "*Page Up step size in lines."
457   :type  'integer
458   :group 'Tinydired)
459
460 (defcustom tinydired-:unwanted-files-regexp
461   "\\.o$\\|~$\\|\\.class\\|\\.pyc"
462   "*Regexp to match files that should not be shown in dired buffer.
463 Set to nil, if you want to see all files.
464 This feature is not used if dired-x is present."
465   :type  '(string :tag "Regexp")
466   :group 'Tinydired)
467
468 (defcustom tinydired-:backup-file-regexp
469   ;;  Like files from CVS: .#ChangeLog.1.3288
470   "\\(\\.bak\\|\\.backup\\|[~#]\\)\\|\\.#$"
471   "*Backup filename regexp, used by advised `dired-flag-backup-files'."
472   :type  '(string :tag "Regexp")
473   :group 'Tinydired)
474
475 (defcustom tinydired-:mput-sites nil
476   "*List of ange-ftp style site location strings, where user can upload files.
477
478 Format '(\"ANGE-FTP-REF\" ..),  ange-ftp-ref is like /login@site:dir/dir/"
479   :type  '(repeat (string :tag "Ange-Ftp"))
480   :group 'Tinydired)
481
482 (defcustom tinydired-:tmp-dir-function 'tinydired-create-tmp-dir
483   "*Create directory for `tinydired-:tmp-dir'."
484   :type  'function
485   :group 'Tinydired)
486
487 (defcustom tinydired-:show-storage-function
488   (function
489    (lambda (args)
490      (message  "%d: %s" (length args)  (ti::list-to-string args))))
491   "*How to show the storage to user. Default is to use `message' function.
492 The function is called with list of files in storage."
493   :type  'function
494   :group 'Tinydired)
495
496 ;;}}}
497 ;;{{{ version
498
499 ;;; ....................................................... &v-version ...
500
501 (eval-and-compile
502   (ti::macrof-version-bug-report
503    "tinydired.el"
504    "tinydired"
505    tinydired-:version-id
506    "$Id: tinydired.el,v 2.49 2007/05/06 23:15:19 jaalto Exp $"
507    '(tinydired-:version-id
508      tinydired-:bind-hook
509      tinydired-:readin-hook
510      tinydired-:load-hook
511      tinydired-:file-store
512      tinydired-:mark-list
513      tinydired-:mput-last-ftp
514      tinydired-:mput-last-ftp
515      tinydired-:previous-buffer
516      tinydired-:dir-copy-buffer
517      tinydired-:tmp-dir
518      tinydired-:tmp-dir-function
519      tinydired-:force-add-keys-flag
520      tinydired-:use-only-one-buffer-flag
521      tinydired-:unwanted-files-regexp
522      tinydired-:download-dir
523      tinydired-:mput-sites
524      tinydired-:show-storage-function
525      tinydired-:page-step
526      ;;  This tells if used has dired-x loaded
527      dired-find-subdir)))
528
529 ;;}}}
530
531 ;;; ########################################################### &Funcs ###
532
533 ;;{{{ code: install, bind, hook control
534
535 ;;; ----------------------------------------------------------------------
536 ;;;
537 (defun tinydired-default-ange-bindings (&optional force)
538   "Add Extra dired bindings. Optionally FORCE adding bindings."
539   (interactive)
540   ;;  "a" for Ange ftp related commands, since the file information
541   ;;  stored is best used in *ftp* buffer itself.
542   (when (or tinydired-:force-add-keys-flag
543             force
544             (eq 'undefined (lookup-key  dired-mode-map "a")))
545     ;; clear this only it the map is not in our use.
546     (if (not (keymapp (lookup-key  dired-mode-map "a")))
547         (define-key dired-mode-map "a" nil))
548     ;;  "b"  for buffer handling
549     (define-key dired-mode-map "abb" 'tinydired-switch-to-ange-ftp-buffer)
550     (define-key dired-mode-map "abp" 'tinydired-switch-to-mput-ange-ftp-buffer)
551     (define-key dired-mode-map "abk" 'tinydired-kill-dired-and-ange-session)
552     ;; Redefine key "q" too. Was 'dired-delete-and-exit'
553     (define-key dired-mode-map "q" 'tinydired-kill-dired-and-ange-session)
554     (when (ti::emacs-p)              ;XEmacs has EFS, these don't work
555       (define-key dired-mode-map "as" 'tinydired-store-filename)
556       (define-key dired-mode-map "ad" 'tinydired-store-delete-filename)
557       (define-key dired-mode-map "aS" 'tinydired-store-add-marked)
558       (define-key dired-mode-map "ar" 'tinydired-store-remove-file)
559       (define-key dired-mode-map "aR" 'tinydired-store-delete-marked)
560       (define-key dired-mode-map "ac" 'tinydired-store-clear)
561       ;;  the "q" is just close to "a" key, no other particular logic used.
562       (define-key dired-mode-map "aq" 'tinydired-store-show)
563       ;;  "g"  for "get"
564       (define-key dired-mode-map "ag" 'tinydired-store-ftp-mget)
565       (define-key dired-mode-map "ap" 'tinydired-store-ftp-mput)))
566   nil)
567
568 ;;; ----------------------------------------------------------------------
569 ;;;
570 (defun tinydired-default-other-bindings (&optional force)
571   "Add extra dired bindings. Optionally FORCE adding bindings."
572   (when (or tinydired-:force-add-keys-flag
573             force
574             (eq 'undefined (lookup-key  dired-mode-map "t")))
575     ;;  make prefix key available for us.
576     (ti::use-prefix-key dired-mode-map "t")
577     ;;  You propably want to do also
578     ;;  (define-key  dired-mode-map "!" 'tinydired-dired-do-shell-command)
579     (define-key  dired-mode-map "t!"    'tinydired-dired-do-shell-command)
580     (define-key  dired-mode-map "t-"    'tinydired-one-dir-up)
581     ;;  "f" for find-file related
582     (ti::use-prefix-key dired-mode-map "tf")
583     (define-key  dired-mode-map "tff"   'tinydired-load-all-marked-files)
584     (define-key  dired-mode-map "tfr"   'tinydired-marked-revert-files)
585     (define-key  dired-mode-map "tg"    'tinydired-refresh-view)
586     (define-key  dired-mode-map "tG"    'tinydired-read-dir-as-is)
587     (ti::use-prefix-key dired-mode-map "tk")
588     (define-key  dired-mode-map "tkk"   'tinydired-kill-lines)
589     (define-key  dired-mode-map "tkm"   'tinydired-kill-marked-lines)
590     (define-key  dired-mode-map "tkM"   'tinydired-kill-unmarked-lines)
591     (define-key  dired-mode-map "tl"    'tinydired-leave-only-lines)
592     (define-key  dired-mode-map "tp"    'tinydired-pop-to-buffer)
593     (define-key  dired-mode-map "te"    'tinydired-ediff)
594     (define-key  dired-mode-map "t<"    'tinydired-shorten-links)
595     (define-key  dired-mode-map "t>"    'tinydired-lenghten-links)
596     ;;  Mark related commands in "m" map
597     (ti::use-prefix-key dired-mode-map "tf")
598     (define-key  dired-mode-map "tme"   'tinydired-mark-files-in-Emacs)
599     (define-key  dired-mode-map "tmd"   'tinydired-mark-today-files)
600     (define-key  dired-mode-map "tmo"   'tinydired-mark-opposite-toggle)
601     (define-key  dired-mode-map "tmr"   'tinydired-mark-read-only-files)
602     (define-key  dired-mode-map "tms"   'tinydired-marks-save)
603     (define-key  dired-mode-map "tmS"   'tinydired-marks-restore)
604     (define-key  dired-mode-map "tmw"   'tinydired-mark-writable-files)
605     (ti::use-prefix-key dired-mode-map "tmv")
606     (define-key  dired-mode-map "tmvv"  'tinydired-mark-vc-files-in-Emacs)
607     (define-key  dired-mode-map "tmvd"  'tinydired-mark-vc-has-diffs)
608     ;;  some special VC functions for marked files in "v" map
609     (ti::use-prefix-key dired-mode-map "tv")
610     (define-key dired-mode-map "tvi"    'tinydired-marked-vc-ci)
611     (define-key dired-mode-map "tvo"    'tinydired-marked-vc-co)
612     (define-key dired-mode-map "tvu"    'tinydired-marked-vc-revert)
613     ;;  Override some Emacs default bindings to better follow
614     ;;  this buffer's content.
615     (define-key dired-mode-map "\M-<"   'tinydired-first-line)
616     (define-key dired-mode-map "\M->"   'tinydired-last-file)
617     (define-key dired-mode-map [(home)]   'tinydired-first-file)
618     (define-key dired-mode-map [(end)]    'tinydired-last-file)
619     (define-key dired-mode-map [(select)] 'tinydired-last-file) ;; 'end' key
620     (define-key dired-mode-map [(prior)]  'tinydired-pgup)
621     (define-key dired-mode-map [(next)]   'tinydired-pgdown))
622   nil)
623
624 ;;; ----------------------------------------------------------------------
625 ;;; - If user has relocated some keys...well, we don't handle those.
626 ;;;
627 (defun tinydired-remove-bindings ()
628   "Remove bindings from this dired session.
629 User must be in dired buffer. Makes the `dired-mode-map'
630 local to current buffer."
631   (interactive)
632   (let* ((list
633           '("abb" "abp" "as" "aS" "ar" "aR" "ac" "aq" "ag" "ap"
634             "t!" "tf" "tg" "tk" "tl" "tp" "t<" "t>"
635             "tmd" "tml" "tms" "tmS" "tmv"
636             "tvi" "tvo" "tvu")))
637     (when (and (memq major-mode '(dired-mode))
638                dired-mode-map)
639       (make-local-variable 'dired-mode-map)
640       (dolist (elt list)
641         (define-key dired-mode-map elt 'tinydired-ignore))
642       ;;  And the rest
643       (define-key dired-mode-map "\M-<"   'beginning-of-buffer)
644       (define-key dired-mode-map "\M->"   'end-of-buffer)
645       (define-key dired-mode-map [(home)] 'beginning-of-buffer)
646       (define-key dired-mode-map [(end)]  'end-of-buffer))))
647
648 ;;; ----------------------------------------------------------------------
649 ;;;
650 ;;;###autoload
651 (defun tinydired-hook-control (&optional remove)
652   "Add hooks to dired mode. Optional REMOVE all hooks inserted by package."
653   (interactive "P")
654   (let* ((list (ti::list-make tinydired-:bind-hook)))
655     (cond
656      (remove
657       (ti::add-hooks 'dired-after-readin-hook tinydired-:readin-hook 'remove)
658       (ti::add-hooks 'dired-mode-hook     tinydired-:bind-hook  'remove))
659      (t
660       ;;  Now, install the package
661       (ti::add-hooks 'dired-after-readin-hook tinydired-:readin-hook)
662       (dolist (x list)                  ;bind the keys
663         (add-hook 'dired-mode-hook x)
664         ;;  This is due to autoload: while the package is beeing loaded,
665         ;;  it should also set the bindings immediately
666         (if (boundp 'dired-mode-map)
667             (funcall x)))))
668     nil))
669
670 ;;; ----------------------------------------------------------------------
671 ;;;
672 (defun tinydired-install (&optional remove)
673   "Install package. Optionally REMOVE."
674   (interactive "P")
675   (cond
676    (remove
677     (tinydired-hook-control   remove)
678     (tinydired-advice-control remove))
679    (t
680     (tinydired-hook-control)
681     (tinydired-advice-control)
682     (tinydired-xemacs-note))))
683
684 ;;}}}
685 ;;{{{ XEmacs compatibility
686
687 ;;; ----------------------------------------------------------------------
688 ;;; Some functions are not found from XEmacs, mimic them
689 ;;;
690 (defun tinydired-dired-unmark-all-files-no-query ()
691   "XEmacs compatibility."
692   (if (fboundp 'dired-unmark-all-files-no-query)
693       (ti::funcall 'dired-unmark-all-files-no-query)
694     (ti::save-line-column-macro nil nil
695       (tinydired-first-line)
696       (while (or (not (eobp))
697                  (not (looking-at "^[ \t]*$")))
698         ;;  Just use brute force for all lines.
699         (dired-unmark 1)))))
700
701 ;;; ----------------------------------------------------------------------
702 ;;;
703 (defun tinydired-xemacs-note ()
704   "Warn that tinydired.el may work improperly in XEmacs."
705   (when (and (ti::xemacs-p)
706              (not (y-or-n-p
707                    "You know that TinyDired's features won't work in XEmacs?")))
708     (tinydired-advice-control 'disable)
709     (error "Abort.")))
710
711 ;;}}}
712 ;;{{{ code: ange-ftp.el
713
714 ;;; ----------------------------------------------------------------------
715 ;;;
716 (defun tinydired-advice-control-old (&optional disable verb)
717   "Activate all advises. Use extra argument to DISABLE all. VERB."
718   (interactive "P")
719   (let* ((re    "^tdd")
720          (doit  t)
721          msg)
722     (ti::verb)
723     (if verb
724         (setq
725          doit
726          (y-or-n-p (format "advices %s: No mistake here? "
727                            (if disable "off" "on")))))
728     (when doit
729       (cond
730        (disable
731         (ad-disable-regexp re)          ;only sets flag
732         (setq msg "All advices deactivated"))
733        (t
734         (ad-enable-regexp re)           ;only sets flag
735         (setq msg "All advices activated")))
736       (ad-update-regexp re)
737       (if verb
738           (message msg)))))
739
740 ;;; ----------------------------------------------------------------------
741 ;;;
742 (defun tinydired-advice-control (&optional disable)
743   "Activate or DISABLE advices in this package."
744   (interactive "P")
745   (ti::advice-control
746    '(ange-ftp-set-binary-mode
747      dired-move-to-end-of-filename
748      ange-ftp-get-pwd
749      ange-ftp-expand-file-name
750      ange-ftp-get-file-entry
751      dired-flag-backup-files
752      dired-find-file)
753    "^tinydired-"
754    disable
755    'verbose
756    "TinyDired advices "))
757
758 ;;; ----------------------------------------------------------------------
759 ;;;
760 (defadvice ange-ftp-set-binary-mode (before tinydired-error-prevent-fix dis)
761   "Sometimes you can get error:
762 ash(nil -4)
763
764   `ange-ftp-set-binary-mode'(\"ftp.uit.no\" \"ftp\")
765
766 Which is due to missing variables
767
768   `ange-ftp-ascii-hash-mark-size'
769   `ange-ftp-binary-hash-mark-size'
770
771 This advice resets them to some default values, so that you don't get
772 errors."
773   (save-excursion
774     (set-buffer (ange-ftp-ftp-process-buffer host user))
775     (if (null ange-ftp-ascii-hash-mark-size)
776         (setq ange-ftp-ascii-hash-mark-size 1024))
777     (if (null ange-ftp-binary-hash-mark-size)
778         (setq ange-ftp-binary-hash-mark-size 1024))))
779
780 ;;}}}
781 ;;{{{ code: misc
782
783 ;;; ----------------------------------------------------------------------
784 ;;; - See dired-repeat-over-lines
785 ;;;
786 (defmacro tinydired-map-over-regexp (re &rest body)
787   "If '(looking-at RE)' then do BODY over all lines matching.
788 Start from current point. The point is positioned at the beginning of line.
789 Buffer read-only is removed.
790
791 The BODY should move the pointer to next file and bol, until eob reached."
792   (`
793    (let* ((end (tinydired-last-file-point))
794           buffer-read-only)
795      (beginning-of-line)
796      (while (and (not (eobp))
797                  (< (point) end))
798        (beginning-of-line)
799        (if (looking-at (, re))
800            (progn
801              (,@ body))
802          (forward-line 1))))))
803
804 ;;; ----------------------------------------------------------------------
805 ;;;
806 (put 'tinydired-map-over-files 'lisp-indent-function 0)
807 (defmacro tinydired-map-over-files (&rest body)
808   "Map over files. No No dirs are included.
809 You must advance the cursor in the BODY. See `tinydired-map-over-regexp'."
810   (`
811    (progn
812      (tinydired-first-file)
813      (tinydired-map-over-regexp "^. +[^d]" (,@ body)))))
814
815 ;;; ----------------------------------------------------------------------
816 ;;;
817 (put 'tinydired-map-over-unmarked 'lisp-indent-function 0)
818 (defmacro tinydired-map-over-unmarked (&rest body)
819   "Map over unmarked lines and execute BODY at the beginning of line.
820 The calling BODY should position the cursor for next search so
821 that current line is skipped when BODY finishes.
822
823 The buffer is writable during mapping."
824   (`
825    (let* (buffer-read-only
826           (ReGexp (dired-marker-regexp)))
827      (progn
828        (tinydired-map-over-files
829         (if (looking-at ReGexp)
830             (forward-line 1)
831           (beginning-of-line)
832           (,@ body)))))))
833
834 ;;; ----------------------------------------------------------------------
835 ;;;
836 (defmacro tinydired-remember-marks (var-sym &optional beg end)
837   "Save mark list to variable VAR-SYM between points BEG and END.
838 START and END defaults to all files"
839   (`
840    (setq (, var-sym)
841          (dired-remember-marks
842           (or (, beg)
843               (tinydired-first-line-point))
844           (or (, end)
845               (tinydired-last-file-point))))))
846
847 ;;; ----------------------------------------------------------------------
848 ;;;
849 (defun tinydired-ignore ()
850   "Ignore message."
851   (interactive)
852   (message "TinyDired: Function is not available in this dired buffer."))
853
854 ;;; ----------------------------------------------------------------------
855 ;;;
856 (defun tinydired-create-tmp-dir ()
857   "Create directory `tinydired-:tmp-dir' if possible."
858   (make-directory (expand-file-name tinydired-:tmp-dir)))
859
860 ;;; ----------------------------------------------------------------------
861 ;;;
862 (defun tinydired-get-tmp-dir ()
863   "Return temp directory with slash at the end."
864   (let* ((dir   tinydired-:tmp-dir)
865          (func  tinydired-:tmp-dir-function))
866     (unless (not (file-exists-p dir))
867       (setq dir (funcall func)))
868     (setq dir (expand-file-name dir))
869     (unless (file-exists-p dir)
870       (error "TinyDired: Directory not exist %s" dir))
871     (file-name-as-directory dir)))
872
873 ;;; ----------------------------------------------------------------------
874 ;;;
875 (defsubst tinydired-get-filename ()
876   "Return only filename without directory."
877   ;;  The (dired-get-filename t) almos does the same, but it _may_
878   ;;  contains slahes.. docs say so.
879   (ti::string-match "\\([^/]+\\)$" 1 (dired-get-filename)))
880
881 ;;; ----------------------------------------------------------------------
882 ;;;
883 (defsubst tinydired-get-mark ()
884   "Return first char to the left. Point is not preserved."
885   (beginning-of-line)
886   (following-char))
887
888 ;;; ----------------------------------------------------------------------
889 ;;;
890 (defsubst tinydired-get-marked-files ()
891   "Signal no errors."
892   (ignore-errors (dired-get-marked-files)))
893
894 ;;; ----------------------------------------------------------------------
895 ;;;
896 (defun tinydired-get-marked-files-no-dir ()
897   "Return LIST of marked files."
898   ;; #todo:  See this code via macroexpand And you'll find test
899   ;;
900   ;;    (if (< nil 0) (nreverse results) results))
901   ;;
902   ;;  Which flags an compile error in XEmacs.
903   ;;
904   (dired-map-over-marks
905    (tinydired-get-filename)
906    nil))
907
908 ;;; ----------------------------------------------------------------------
909 ;;;
910 (defsubst tinydired-first-line-point ()
911   "Return first file point."
912   (save-excursion
913     (tinydired-first-line)
914     (line-beginning-position)))
915
916 ;;; ----------------------------------------------------------------------
917 ;;;
918 (defsubst tinydired-last-file-point ()
919   "Return last file point."
920   (save-excursion (tinydired-last-file) (line-end-position)))
921
922 ;;; ----------------------------------------------------------------------
923 ;;;
924 (defsubst tinydired-mark-re (re &optional unmark)
925   "Mark files matching RE. Give prefix argument to UNMARK."
926   (save-excursion
927     (ti::pmin)
928     (while (re-search-forward re nil t)
929       (if unmark
930           (dired-unmark 1)
931         (dired-mark 1)))))
932
933 ;;; ----------------------------------------------------------------------
934 ;;;
935 (defsubst tinydired-mark-file-list (list &optional unmark)
936   "Mark files in LIST. Give prefix argument to UNMARK."
937   (dolist (elt (ti::list-make list))
938     (tinydired-mark-re (concat (regexp-quote elt) "$") unmark)))
939
940 ;;; ----------------------------------------------------------------------
941 ;;;
942 (defun tinydired-feature-p (arg)
943   "Check if we already have this functionality in dired. See ARG from code."
944   ;;  Emacs with with `dired-x', which I just noticed had some of
945   ;;  the same functionality. We don't use TDD if those
946   ;;  are present in some cases.
947   (cond
948    ((eq arg 'auto-delete)
949     ;;  see dired-omit-files-p
950     (and (featurep 'dired-x)
951          (> emacs-minor-version 27)))
952    (t
953     nil)))
954
955 ;;; ----------------------------------------------------------------------
956 ;;;
957 ;;;
958 (defun tinydired-normal-buffer-p ()
959   "Check if buffer's first line look like dired."
960   (interactive)
961   (and (not (ti::buffer-narrowed-p))
962        (save-excursion
963          (ti::pmin)
964          (and (looking-at "^[ \t]+\\([a-z]:\\)?/")
965 ;;;  In VAX these don't exist.
966 ;;;
967 ;;;           (forward-line 1)
968 ;;;           (looking-at "^[ \t]+total[ \t]+[0-9]")
969               t))))
970
971 ;;; ----------------------------------------------------------------------
972 ;;;
973 (defun tinydired-kill-files ()
974   "After each dired read, remove unwanted files."
975   (let* ((re     tinydired-:unwanted-files-regexp)
976          buffer-read-only)
977     (unless (tinydired-feature-p 'auto-delete)
978       ;;  Is this new directory buffer ..
979       (if (and (eq major-mode 'dired-mode)
980                (stringp re))
981           (flush-lines re))             ;don't wanna see these
982       nil)))
983
984 ;;; ----------------------------------------------------------------------
985 ;;;
986 (defun tinydired-file-list (&optional arg mode)
987   "Gets all files/dir entries in the view.
988 The ARG is `dired-get-filename' parameter.
989
990 Input:
991
992   ARG           If non-nil, no absolute names
993   MODE          if 're then make regexp out of files.
994                 if 'files then return just filenames
995
996 Return list:
997
998   (re re ..)            If mode is 're
999   (file file ...)       If mode is 'plain
1000   ((mark file) ..)      default
1001
1002 The `mark' is first character in the left for file or dir."
1003   (let* (last-point
1004          list
1005          file)
1006     (save-excursion
1007       (setq last-point (tinydired-last-file-point))
1008       (tinydired-first-line)
1009       (if (setq file (ignore-errors (dired-get-filename arg)))
1010           (cond
1011            ((eq mode 're)
1012             (beginning-of-line)
1013             (if (looking-at dired-re-sym)
1014                 (push (concat (regexp-quote file) " +->") list)
1015               (push (format " %s$" (regexp-quote file)) list)))
1016            ((eq mode 'files)
1017             (push file list))
1018            (t
1019             (push (list (tinydired-get-mark) file) list))))
1020       (while (< (point) last-point)
1021         (dired-next-line 1)
1022         (if (setq file (ignore-errors (dired-get-filename arg)))
1023             (cond
1024              ((eq mode 're)
1025               (beginning-of-line)
1026               (if (looking-at dired-re-sym)
1027                   (push (concat (regexp-quote file) " +->") list)
1028                 (push (format " %s$" (regexp-quote file)) list)))
1029              ((eq mode 'files)
1030               (push file list))
1031              (t
1032               (push (list (tinydired-get-mark) file) list))))))
1033     list))
1034
1035 ;;; ----------------------------------------------------------------------
1036 ;;;
1037 (defun tinydired-sort-dir ()
1038   "Put directories first in dired listing."
1039   (let (buffer-read-only
1040         marks
1041         p1 p2                           ;points
1042         region)
1043     ;;  - Buffer gets narrowed in some dired internal operations, like
1044     ;;    pressing "l", dired-do-redisplay
1045     ;;  - We do nothing in these cases
1046     ;;
1047     ;;  - We have to save position, because e.g. pressing "Z" to
1048     ;;    compress file, causes reading the whole dir --> point moved.
1049     ;;
1050     (when (tinydired-normal-buffer-p)
1051       (ti::save-with-marker-macro
1052         (tinydired-first-line)
1053         (tinydired-remember-marks marks (point))
1054         (tinydired-dired-unmark-all-files-no-query) ; sort goes nuts otherwise
1055         (message "")                 ; stupid message from dired-un...
1056         ;; Sort regexp by
1057         ;; 19 Nov 1995, sof@dcs.glasgow.ac.uk (Sigbjorn Finne), comp.Emacs
1058         ;;
1059         (tinydired-first-line) (beginning-of-line)
1060         (sort-regexp-fields t "^.*$" "[ ]*." (point) (point-max))
1061         ;;  now, We prefer to have dirs first, and then links, allthough
1062         ;;  some links may be dirs (we can't know anything about links)
1063         ;;
1064         (ti::pmin)
1065         (when (re-search-forward "^[ \t]+lr" nil t)
1066           (setq p1 (line-beginning-position))
1067           ;;  We know that dirs are after links, because the listing is
1068           ;;  sorted.
1069           ;;
1070           (re-search-forward "^[ \t]+d" nil t)
1071           (setq p2 (line-beginning-position))
1072           (setq region (buffer-substring p1 p2))
1073           (delete-region p1 p2)
1074           (re-search-forward "^[ \t]+-" nil t) ;go after dirs
1075           (beginning-of-line)
1076           (insert region))
1077         (dired-mark-remembered marks)
1078         (set-buffer-modified-p nil)))))
1079
1080 ;;; ----------------------------------------------------------------------
1081 ;;;
1082 (defun tinydired-dir-original (dir &optional buffer)
1083   "Do same as `dired-insert-directory'.
1084 Insert DIR to BUFFER, which defaults to `tinydired-:dir-copy-buffer'"
1085   (save-excursion
1086     ;;  See dired.el dired-readin-insert
1087     (ti::temp-buffer (or buffer tinydired-:dir-copy-buffer) 'clear)
1088     (set-buffer (or buffer tinydired-:dir-copy-buffer))
1089     (insert-directory (expand-file-name dir)
1090                       dired-listing-switches nil t)))
1091
1092 ;;; ----------------------------------------------------------------------
1093 ;;;
1094 (defun tinydired-dir-original-get-line (file)
1095   "Return original line for FILE.
1096 Be sure you have called `tinydired-dir-original' first.
1097 Signal no error. Use `regexp-quote' for FILE if it contains unusual characters.
1098
1099 Return:
1100   line
1101   nil   ,no line was found"
1102   (save-excursion
1103     (set-buffer tinydired-:dir-copy-buffer)
1104     (ti::pmin)
1105     ;;  Pick first match
1106     (if (re-search-forward (concat " " file) nil t)
1107         (ti::read-current-line))))
1108
1109 ;;}}}
1110 ;;{{{ code: interactive
1111
1112 ;;; ----------------------------------------------------------------------
1113 ;;;
1114 ;;;###autoload
1115 (defun tinydired-ediff (file &optional switches)
1116   "Compare file at point with file FILE using `ediff'.
1117 FILE defaults to the file at the mark.
1118 The prompted-for file is the first file given to `ediff'.
1119 With prefix arg, prompt for second argument SWITCHES,
1120  which is options for `diff'."
1121   (interactive
1122    (let ((default (if (mark t)
1123                       (save-excursion (goto-char (mark t))
1124                                       (dired-get-filename t t)))))
1125      (list
1126       (read-file-name                   ;ARG 1
1127        (format "Ediff %s with: %s"
1128                (dired-get-filename t)
1129                (if default
1130                    (concat "(default " default ") ")
1131                  ""))
1132        (dired-current-directory) default t)
1133       (if current-prefix-arg            ;ARG 2
1134           (read-string
1135            "Options for diff: "
1136            (if (stringp diff-switches)
1137                diff-switches
1138              (mapconcat 'identity diff-switches " ")))))))
1139   ;; Interactive part end
1140   (ediff-files file
1141                (dired-get-filename t) switches))
1142
1143 ;;; ----------------------------------------------------------------------
1144 ;;;
1145 ;;;###autoload
1146 (defun tinydired-read-dir-as-is ()
1147   "Read the directory without any filtering."
1148   (interactive)
1149   (let* (dired-after-readin-hook)
1150     (revert-buffer)))
1151
1152 ;;; ----------------------------------------------------------------------
1153 ;;;
1154 ;;;###autoload
1155 (defun tinydired-mark-files-in-Emacs ()
1156   "Mark all files in current directory that are in Emacs."
1157   (interactive)
1158   (let* ((dir (expand-file-name dired-directory)) ;get rid of "~"
1159          (list (ti::dolist-buffer-list
1160                 (and (buffer-file-name)
1161                      (string-match (regexp-quote dir) (buffer-file-name))))))
1162     (if (null dir)
1163         (setq dir dir))                 ;Shut up byteCompiler
1164     (dolist (elt list)
1165       (save-excursion
1166         (tinydired-first-file)
1167         (if (re-search-forward elt nil t)
1168             (dired-mark 1))))))
1169
1170 ;;; ----------------------------------------------------------------------
1171 ;;;
1172 ;;;###autoload
1173 (defun tinydired-marked-revert-files (&optional arg)
1174   "Revert ie. replace files in Emacs with true copies in directory.
1175 If ARG is non-nil, remove any marks if file was loaded.
1176
1177 Exceptions:
1178   Only reload files in Emacs whose modify flag is non-nil.
1179   If file does not exist in Emacs, do nothing."
1180   (interactive "P")
1181   (let* ((list (tinydired-get-marked-files))
1182          buffer)
1183     (dolist (file list)
1184       (when (setq buffer (get-file-buffer file))
1185         (with-current-buffer buffer
1186           (unless (buffer-modified-p)
1187             (revert-buffer nil t)       ;no confirmation
1188             (setq buffer 'done) )))
1189       (when (and arg  (eq 'done buffer))
1190         (tinydired-mark-re
1191          (regexp-quote (file-name-nondirectory file)) 'unmark)))))
1192
1193 ;;; ----------------------------------------------------------------------
1194 ;;; - It's lot faster to use direct command, than search the buffer
1195 ;;;   for ".." and use "f" or click mouse over it.
1196 ;;;
1197 ;;;###autoload
1198 (defun tinydired-one-dir-up ()
1199   "Go up one directory."
1200   (interactive)
1201   (find-file (concat dired-directory "..")))
1202
1203 ;;; ----------------------------------------------------------------------
1204 ;;;
1205 ;;;###autoload
1206 (defun tinydired-dired-do-shell-command (command &optional arg)
1207   "Like `dired-do-shell-command', but run running command in dired ange-ftp.
1208 This is not remote shell, but instead it
1209 transfers the file to your local system and then executes the dired
1210 command on the file.
1211
1212 Remember: Every time you run this command this files are copied _blindly_
1213 to your local directory. No file cache information is kept.
1214
1215 Input:
1216
1217   COMMAND
1218   ARG
1219
1220 References:
1221
1222   `tinydired-:tmp-dir'"
1223   (interactive
1224    (list
1225     (dired-read-shell-command
1226      (concat "! on "
1227              "%s: ")
1228      current-prefix-arg
1229      (dired-get-marked-files
1230       t current-prefix-arg))
1231     current-prefix-arg))
1232   (let* ((to-dir  (tinydired-get-tmp-dir))
1233          (ange    (ange-ftp-ftp-name dired-directory))
1234          (on-each (not (string-match "\\*" command)))
1235          host user dir
1236          file-list
1237          list)
1238     (cond
1239      ((null ange)
1240       ;; Simple local dired.
1241       (dired-do-shell-command command arg))
1242      (t
1243       (setq host  (nth 0 ange)
1244             user  (nth 1 ange)
1245             dir   (nth 2 ange))
1246       (setq file-list (dired-get-marked-files t))
1247       (ti::file-ange-file-handle 'get user host dir to-dir file-list 'foreground)
1248       (dolist (file file-list)       ; All directory to every filename
1249         (push (concat to-dir file)  list))
1250       (setq file-list list)
1251       ;; ......................................... copy from dired-aux ...
1252       (if on-each
1253           (dired-bunch-files
1254            (- 10000 (length command))
1255            (function
1256             (lambda (&rest files)
1257               (dired-run-shell-command
1258                (dired-shell-stuff-it command files t arg))))
1259            nil
1260            file-list)
1261         ;; ... ... ... ... ... ... ... ... ... ... ... ... ... .. then ...
1262         ;; execute the shell command
1263         (dired-run-shell-command
1264          (dired-shell-stuff-it command file-list nil arg)))))))
1265
1266 ;;; ----------------------------------------------------------------------
1267 ;;;
1268 ;;;###autoload
1269 (defun tinydired-lenghten-links ()
1270   "Opposite to `tinydired-shorten-links'.
1271 This may take a while, because the whole directory structure must
1272 be read again."
1273   (interactive)
1274   (let* ((line   (ti::current-line-number))
1275          file
1276          marks
1277          buffer-read-only)
1278     (when (tinydired-normal-buffer-p)
1279       ;;        Now create copy of original directory.
1280       (tinydired-dir-original dired-directory)
1281       (tinydired-remember-marks marks)
1282       (tinydired-dired-unmark-all-files-no-query)
1283       (message "")
1284       (dired-mark-symlinks nil)
1285       ;;   This didn't update full line, only the data part, not the
1286       ;;   linked name portion "->"
1287       ;;      (dired-do-redisplay)
1288       (dired-map-over-marks
1289        (progn
1290          (setq file (dired-get-filename 'no-dir))
1291          (setq line (tinydired-dir-original-get-line (regexp-quote file)))
1292          ;;  now, delete line and relace it with original entry.
1293          (when line
1294            (beginning-of-line)
1295            (re-search-forward " l")
1296            (backward-char 1)
1297            (delete-region (point) (line-end-position))
1298            (insert line) ))
1299        nil)
1300       (dired-mark-symlinks 'unmark)
1301       (if marks
1302           (dired-mark-remembered marks))
1303       (set-buffer-modified-p nil))))
1304
1305 ;;; ----------------------------------------------------------------------
1306 ;;; - It's awfull to see 30 linked files whyen they don't fit on one line...
1307 ;;;
1308 ;;;###autoload
1309 (defun tinydired-shorten-links ()
1310   "Shortens all linked files. The link part is removed."
1311   (interactive)
1312   (let* ((line (ti::current-line-number))
1313          buffer-read-only)
1314     (when (tinydired-normal-buffer-p)
1315       (ti::pmin)
1316       (while (not (eobp))
1317         (if (looking-at ".* +->\\([^\n]+\\)")
1318             (ti::replace-match 1))
1319         (forward-line 1))
1320       (goto-line line)
1321       (dired-move-to-filename))))
1322
1323 ;;; ----------------------------------------------------------------------
1324 ;;;
1325 ;;;###autoload
1326 (defun tinydired-marks-save ()
1327   "Save mark list to private storage.
1328 Use this function if you know next operation will remove the marks.
1329 You can get the marks back with `tinydired-marks-restore'."
1330   (interactive)
1331   (save-excursion                       ;due to next command
1332     (tinydired-remember-marks tinydired-:mark-list)
1333     (message "TinyDired: Marks saved.")))
1334
1335 ;;; ----------------------------------------------------------------------
1336 ;;;
1337 ;;;###autoload
1338 (defun tinydired-marks-restore ()
1339   "Restore mark list saved by `tinydired-marks-save'."
1340   (interactive)
1341   (if (null tinydired-:mark-list)
1342       (message
1343        (substitute-command-keys
1344         "No marks saved. Use '\\[tinydired-marks-save]' first."))
1345     (dired-mark-remembered tinydired-:mark-list)))
1346
1347 ;;; ----------------------------------------------------------------------
1348 ;;;
1349 ;;;###autoload
1350 (defun tinydired-pgup ()
1351   "Move cursor to _last_ file in dired mode."
1352   (interactive)
1353   (dired-next-line (- tinydired-:page-step))
1354   (if (bobp)
1355       (tinydired-first-line)))
1356
1357 ;;; ----------------------------------------------------------------------
1358 ;;;
1359 ;;;###autoload
1360 (defun tinydired-pgdown ()
1361   "Move cursor up."
1362   (interactive)
1363   (dired-next-line tinydired-:page-step)
1364   (if (eobp)
1365       (tinydired-last-file)))
1366
1367 ;;; ----------------------------------------------------------------------
1368 ;;;
1369 ;;;###autoload
1370 (defun tinydired-first-line ()
1371   "Move to first _line_ in dired."
1372   (interactive)
1373   (let* (point)
1374     (save-excursion
1375       (ti::pmin)
1376       (forward-line 2)
1377       (when (looking-at "^  .*[rwx]")
1378         (dired-move-to-filename)
1379         (setq point (point))))
1380     (if point
1381         (goto-char point)
1382       ;; Then,  it's some strange non-unix propably ...
1383       nil)))
1384
1385 ;;; ----------------------------------------------------------------------
1386 ;;; - Supposing the directory is in order...dirs first then files...
1387 ;;;
1388 ;;;###autoload
1389 (defun tinydired-first-file ()
1390   "Move to first file in dired."
1391   (interactive)
1392   (let* (point)
1393     (save-excursion
1394       (ti::pmin)
1395       (while (and (null point)
1396                   (not (eobp)))
1397         (forward-line 1)
1398         (dired-move-to-filename)
1399         (unless (eq 0 (current-column))
1400           (setq point (point))) ))
1401     (if point
1402         (goto-char point)
1403       ;; Then, it's some strange non-unix propably ...
1404       nil)))
1405
1406 ;;; ----------------------------------------------------------------------
1407 ;;;
1408 ;;;###autoload
1409 (defun tinydired-last-file ()
1410   "Move to last file in dired."
1411   (interactive)
1412   (let* (point)
1413     (save-excursion
1414       (ti::pmax)
1415       (while (and (null point)
1416                   (not (bobp)))
1417         (forward-line -1)
1418         (dired-move-to-filename)
1419         (unless (eq 0 (current-column))
1420           (setq point (point))) ))
1421     (if point
1422         (goto-char point)
1423       ;; Then, it's some strange non-unix propably ...
1424       nil)))
1425
1426 ;;; ----------------------------------------------------------------------
1427 ;;;
1428 (defun tinydired-kill-marked-lines ()
1429   "Remove lines that are unmarked."
1430   (interactive)
1431   (let (buffer-read-only
1432         list)
1433     (dired-map-over-marks
1434      (push (regexp-quote (ti::read-current-line)) list)
1435      nil)
1436     (dolist (re list)
1437       (ti::pmin)
1438       (if (re-search-forward re nil t)
1439           (ti::buffer-kill-line)))))
1440
1441 ;;; ----------------------------------------------------------------------
1442 ;;;
1443 ;;;###autoload
1444 (defun tinydired-kill-unmarked-lines ()
1445   "Remove unmarked lines. Ignore directories and symlinks."
1446   (interactive)
1447   (tinydired-map-over-unmarked
1448    (let* (char)
1449      ;; We're at the beginning of line, suppose std unix 'ls'
1450      ;; drwx--x--x
1451      (setq char (buffer-substring (+ 2 (point)) (+ 3 (point))))
1452      (if (not (or (string= char "d")
1453                   (string= char "l")))
1454          (ti::buffer-kill-line)
1455        ;; Continue mapping
1456        (end-of-line))))
1457   (tinydired-first-file))
1458
1459 ;;; ----------------------------------------------------------------------
1460 ;;;
1461 ;;;###autoload
1462 (defun tinydired-kill-lines (re)
1463   "Delete lines matching RE."
1464   (interactive "sKill files re: ")
1465   (let* (buffer-read-only)
1466     (unless (ti::nil-p re)
1467       (ti::save-line-column-macro (tinydired-first-file) (dired-move-to-filename)
1468         (tinydired-first-file) ;; do this in ti::save-line-column-macro
1469         (flush-lines re)))))
1470
1471 ;;; ----------------------------------------------------------------------
1472 ;;;
1473 ;;;###autoload
1474 (defun tinydired-leave-only-lines (re)
1475   "Leave only lines matching RE. Directory lines are skipped.
1476 You can easily undo this with reverting the buffer (dired \"g\")."
1477   (interactive "sLeave regexp: ")
1478   (unless (ti::nil-p re)
1479     (ti::pmin)
1480     (tinydired-map-over-files
1481      (if (string-match re (ti::read-current-line))
1482          (forward-line 1)
1483        (ti::buffer-kill-line)))
1484     (tinydired-first-file)))
1485
1486 ;;; ----------------------------------------------------------------------
1487 ;;;
1488 ;;;###autoload
1489 (defun tinydired-pop-to-buffer ()
1490   "Pop to buffer if it exists in Emacs."
1491   (interactive)
1492   (let* ((file (ignore-errors (dired-get-filename)))
1493          buffer)
1494     (cond
1495      ((and (stringp file)
1496            (setq buffer (get-file-buffer file)))
1497       (pop-to-buffer buffer))
1498      (t
1499       (message (format "TinyDired: Can't pop ... Not in Emacs. [%s]"
1500                        file))))))
1501
1502 ;;; ----------------------------------------------------------------------
1503 ;;; - This behaves differently than dired-x.el dired-do-find-marked-files
1504 ;;;
1505 ;;;###autoload
1506 (defun tinydired-mark-today-files ()
1507   "Mark all files, not dirs, that are created today.
1508 Point sits on first today file. If no today's files are found, point stays
1509 on current filename."
1510   (interactive)
1511   (let* ((list   (ti::date-time-elements))
1512          (line   (ti::current-line-number))
1513          ;;      1024 Oct  3
1514          (re     (concat ".*[0-9] " (nth 5 list) " +"
1515                          (int-to-string (nth 0 list))
1516                          " +"
1517                          ;;  This year's file have time in this field
1518                          "[0-9]+:")))
1519     (tinydired-map-over-files
1520      (if (not (looking-at re))
1521          (forward-line)
1522        (dired-mark 1) ))
1523     (tinydired-first-file)
1524     (if (re-search-forward re nil t)
1525         (dired-move-to-filename)
1526       (goto-line line)
1527       (dired-move-to-filename))))
1528
1529 ;;; ----------------------------------------------------------------------
1530 ;;;
1531 (defun tinydired-mark-writable-files ()
1532   "Mark Your files that have writable flag set."
1533   (interactive)
1534   (let* ((re    ".*.w..[-w]..[-w]. "))
1535     (tinydired-map-over-files
1536      (if (not (looking-at re))
1537          (forward-line)
1538        (dired-mark 1)))))
1539
1540 ;;; ----------------------------------------------------------------------
1541 ;;;
1542 (defun tinydired-mark-read-only-files ()
1543   "Mark Your files that have writable flag set."
1544   (interactive)
1545   (let* ((re    ".*r-.[-r]..[-r].. "))
1546     (tinydired-map-over-files
1547      (if (not (looking-at re))
1548          (forward-line)
1549        (dired-mark 1)))))
1550
1551 ;;; ----------------------------------------------------------------------
1552 ;;;
1553 (defun tinydired-mark-opposite-toggle ()
1554   "Mark opposite files.
1555 Ie. if you have marked some files, unmark those and mark all other files."
1556   (interactive)
1557   (let* ((re  (dired-marker-regexp)))
1558     (ti::save-line-column-macro nil nil
1559       (tinydired-map-over-files
1560        (beginning-of-line)
1561        (if (looking-at re)
1562            (dired-unmark 1)
1563          (dired-mark 1))))))
1564
1565 ;;; ----------------------------------------------------------------------
1566 ;;; - This behaves differently than dired-x.el dired-do-find-marked-files
1567 ;;;
1568 ;;;###autoload
1569 (defun tinydired-mark-vc-files-in-Emacs (&optional unmark verb)
1570   "Mark all files in the current _view_ that are in Emacs _and_ in VC control.
1571 Optionally UNMARK. VERB."
1572   (interactive)
1573   (let* ((dir           (expand-file-name dired-directory))
1574          (msg           (if unmark
1575                             "Unmarking..."
1576                           "Marking..."))
1577          list)
1578     (ti::verb)
1579     (if (null dir)
1580         (setq dir dir))                 ;Shut up XEmacs 19.14 ByteComp
1581     (setq list
1582           (ti::dolist-buffer-list
1583            (and buffer-file-name
1584                 (string-match dir buffer-file-name)
1585                 (vc-registered buffer-file-name))))
1586     (if verb
1587         (message msg))
1588     (cond
1589      ((and (null list)   verb)
1590       (message "Tinydired: No VC files of this dir in Emacs."))
1591      (t
1592       (tinydired-mark-file-list list unmark)
1593       (if verb
1594           (message (concat msg "Done")))))))
1595
1596 ;;; ----------------------------------------------------------------------
1597 ;;;
1598 ;;;###autoload
1599 (defun tinydired-refresh-view (&optional verb)
1600   "Refresh current dired view.
1601 If you have used `tinydired-leave-only-lines' and have done some changes to
1602 the files. You can use this function to re-read the current view.
1603
1604 The dired \"g\" will load full view back. This instead caches the
1605 current view, executes read, and deletes lines that weren't in the
1606 cache --> you get refreshed view. All this may take a while...
1607
1608 Input:
1609
1610  VERB       Verbose messages
1611
1612 Return:
1613
1614  t              if refreshed
1615  nil"
1616   (interactive)
1617   (let* ((cache         (tinydired-file-list 'no-path-names 're))
1618          (line          (ti::current-line-number)) ;save user position
1619          (re            "")
1620          buffer-read-only               ;allow write
1621          marks)
1622     (ti::verb)
1623     (cond
1624      (cache
1625       (setq re (mapconcat 'concat cache "\\|"))
1626       (setq marks
1627             (dired-remember-marks
1628              (tinydired-first-line-point)
1629              (tinydired-last-file-point)))
1630       ;; sort goes nuts otherwise
1631       (tinydired-dired-unmark-all-files-no-query)
1632       (message "")                   ; stupid message from dired-un...
1633       (revert-buffer)
1634       (ti::pmin)
1635       (forward-line 2)                  ;leave headers
1636       (tinydired-first-file)
1637       (beginning-of-line)
1638       (let ((case-fold-search nil))     ;case sensitive
1639         (delete-non-matching-lines re))
1640       (dired-mark-remembered marks)
1641       (goto-line line)
1642       (dired-move-to-filename)
1643       (if verb
1644           (message "TinyDired: Refresh done."))
1645       t)
1646      (t
1647       (if verb
1648           (message "TinyDired: Can't cache view."))
1649       nil))))
1650
1651 ;;; ----------------------------------------------------------------------
1652 ;;; - This behaves differently than dired-x.el dired-do-find-marked-files
1653 ;;;
1654 ;;;###autoload
1655 (defun tinydired-load-all-marked-files (&optional arg verb)
1656   "Load all marked files into Emacs.
1657 Does not load files which are already in Emacs.
1658 If ARG is non-nil, remove mark if file was loaded. VERB."
1659   (interactive "P")
1660   (let* ((files         (tinydired-get-marked-files))
1661          (loaded        0)
1662          (not-loaded    0)
1663          (all           0))
1664     (ti::verb)
1665     (cond
1666      ((and verb (null files))
1667       (message "Tinydired: No marked files."))
1668      ((y-or-n-p "Tinydired: Load all marked files, No kidding? ")
1669       (dolist (file files)
1670         (incf  all)
1671         (if (get-file-buffer file)
1672             (incf  not-loaded)
1673           (incf  loaded)
1674           (find-file-noselect file))
1675         (if arg
1676             (save-excursion (dired-unmark 1))))))
1677     (if verb
1678         (cond
1679          ((eq all not-loaded)
1680           (message "Hmm, all files are in Emacs already.."))
1681          (t
1682           (message "Tinydired: %s files loaded." loaded))))))
1683
1684 ;;}}}
1685 ;;{{{ code: vc special
1686
1687 ;;; ----------------------------------------------------------------------
1688 ;;;
1689 (defun tinydired-mark-vc-has-diffs (&optional arg)
1690   "Leave mark to files: VC controlled, have diffs and are in Emacs.
1691 If ARG is non-nil, examine file whether it was in Emacs or not.
1692
1693 Note:
1694   Please be patient, taking diffs may be slow per file."
1695   (interactive)
1696   (let* ((list  (tinydired-get-marked-files))
1697          fn
1698          buffer
1699          vc-reg-stat
1700          diff-no-stat)
1701     (dolist (file list)
1702       (setq fn          (file-name-nondirectory file)
1703             buffer      (get-file-buffer file)
1704             vc-reg-stat (vc-registered file))
1705       (cond
1706        ((or (not vc-reg-stat)
1707             ;;  Not exist in Emacs, do not bother looking
1708             (and (null arg) (null buffer)))
1709         (tinydired-mark-re (regexp-quote fn) 'unmark))
1710        (t
1711         (setq diff-no-stat (vc-workfile-unchanged-p file 'get-diffs))
1712         (if diff-no-stat
1713             (tinydired-mark-re (regexp-quote fn) 'unmark)))))))
1714
1715 ;;; ----------------------------------------------------------------------
1716 ;;;
1717 (defun tinydired-marked-vc-revert (&optional arg verb)
1718   "Revert all version controlled/no changed/marked files. Ignore ARG. VERB."
1719   (interactive "P")
1720   (let* ((list          (tinydired-get-marked-files))
1721          (display       (if list t))
1722          (vc-dired-mode nil)            ;turn mode off
1723          (count         0)
1724          (handled       0)
1725          load
1726          buffer
1727          vc-reg-stat
1728          diff-no-stat)
1729     (ti::verb)
1730     (dolist (file list)
1731       (setq buffer       (get-file-buffer file)
1732             vc-reg-stat  (vc-registered file)
1733             load         nil
1734             diff-no-stat nil)
1735       (incf  count)
1736       ;; ... ... ... ... ... ... ... ... ... ... ... ... possible load . .
1737       (when (and (null buffer)
1738                  (file-writable-p file)
1739                  vc-reg-stat)
1740         (setq buffer (find-file-noselect file)
1741               load   t))
1742       (if buffer
1743           (setq diff-no-stat (vc-workfile-unchanged-p file 'get-diffs)))
1744       ;; ... ... ... ... ... ... ... ... ... ... ... ... ... .. handle . .
1745       (cond
1746        ((null buffer)
1747         nil)                            ;no file, no vc controlled
1748        (diff-no-stat
1749         (incf  handled)
1750         (save-window-excursion
1751           (vc-next-action-on-file file 'verbose)
1752           (if load
1753               (kill-buffer buffer))))))
1754     (if display
1755         (dired-do-redisplay))
1756     (if verb
1757         (message "Tinydired: VC revert:  %s/%s handled "  handled count))))
1758
1759 ;;; ----------------------------------------------------------------------
1760 ;;;
1761 (defun tinydired-marked-vc-co (&optional arg)
1762   "Check Out all marked files and load them inside Emacs.
1763 Do some checking, before doing co.
1764 o  if file is writable, skip over.
1765 o  if file is not in RCS, skip over.
1766
1767 Optional ARG skips all load confirmations.
1768
1769 Marks are left only to files which were loaded into Emacs."
1770   (interactive "P")
1771   (let* ((list          (tinydired-get-marked-files))
1772          (dired-vc-mode nil)            ;turn mode off
1773          (count         0)
1774          (loaded        0)
1775          (handled       0)
1776          fn
1777          buffer
1778          load
1779          vc-reg-stat
1780          modify-stat
1781          read-stat)
1782     (if dired-vc-mode
1783         (setq dired-vc-mode nil))       ;ByteComp silencer
1784     (dolist (file list)
1785       (setq fn          (file-name-nondirectory file)
1786             buffer      (get-file-buffer file)
1787             vc-reg-stat (vc-registered file)
1788             load        nil)
1789       (if buffer                      ;read stat only if it's in Emacs
1790           (save-excursion
1791             (set-buffer buffer)
1792             (setq modify-stat (buffer-modified-p)
1793                   read-stat   buffer-read-only)))
1794       (incf  count)
1795       ;; ... ... ... ... ... ... ... ... ... ... ... ... possible load . .
1796       (cond
1797        ((and (null buffer)
1798              vc-reg-stat                        ;; in VC
1799              (not (file-writable-p file))       ;; -r--r--r--
1800              (or arg
1801                  (y-or-n-p
1802                   (concat "file " fn " not in Emacs. Load? " ))))
1803         (incf  loaded)
1804         (setq buffer (find-file-noselect file)
1805               load   t)))
1806       ;; ... ... ... ... ... ... ... ... ... ... ... ... ... .. handle . .
1807       (cond
1808        (load
1809         ;; nothing
1810         nil)
1811        ((and buffer ;; in Emacs, be extra carefull
1812              vc-reg-stat                        ;; in VC
1813              (not (file-writable-p file))       ;; -r--
1814              (null modify-stat)                 ;; %*
1815              read-stat)                         ;; %%
1816         ;; --> no-op, valid state
1817         nil)
1818        (t ;; User has modified it!
1819         ;;  This situation may occur very easily
1820         ;;  - You load -r-- file in Emacs that's in VC
1821         ;;  - you want to temporary play with it, like changing one
1822         ;;    flag in .mak temporarily
1823         ;;  - you go and M-x toggle-read-only, change it, C-x C-s
1824         ;;  ...
1825         ;;  Now you have modified the read-only file !
1826         (setq buffer nil)))
1827       (cond
1828        ((null buffer)
1829         ;;     18 15:09 test3.txt
1830         (tinydired-mark-re (concat "[0-9] +" fn) 'unmark)
1831         nil)
1832        (t
1833         (save-window-excursion
1834           (incf  handled)
1835           (vc-next-action-on-file file 'verbose)))))
1836     (if (not (eq 0 handled))
1837         (dired-do-redisplay))
1838     (message (format "Tinydired: VC co: %s/%s handled, loaded %s"
1839                      handled count loaded))))
1840
1841 ;;; ----------------------------------------------------------------------
1842 ;;; - This is vastly different than C-x v v in dired mode
1843 ;;;
1844 (defun tinydired-marked-vc-ci (&optional arg &optional verb)
1845   "Check In all marked files and load them inside Emacs. Ignore ARG.
1846 Do some heavy checking, before doing ci.
1847 o  if file is not writable, skip over
1848 o  if file is not in Emacs, load it first
1849 o  if file is in Emacs, but read only, suppose no diffs
1850 o  if file is in Emacs, check rcsdiff, --> do nothing if no diffs
1851 o  if file is in Emacs, check rcsdiff, if file not saved, offer save
1852
1853 Notice, that this function enters `recursive-edit' if it thinks file should
1854 be Checked In. Use \\[exit-recursive-edit] to get back to this function
1855 and continue with rest of the files.
1856
1857 Recursive edit is shown with those [ ] marks in the modeline.
1858 VERB print verbose messages.
1859
1860 Note
1861
1862   There is plenty of messages for each file in marked, because
1863   used should know if the marked file couldn't be processed with ci.
1864
1865   Marks are removed from handled files.
1866
1867 Bugs:
1868
1869   This function automatically removes marks from files where user has
1870   used recursive edit. If user didn't ci the file, this program
1871   can't know that.
1872
1873   Anyway, the mark is gone."
1874   (interactive "P")
1875   (let* ((list          (tinydired-get-marked-files))
1876          (count         0)
1877          (handled       0)
1878          (loaded        0)
1879          fn
1880          buffer
1881          load                           ;flag
1882          diff-no-stat
1883          modify-stat
1884          read-stat
1885          vc-reg-stat)
1886     (ti::verb)
1887     (if (and (null vc-dired-mode)
1888              (y-or-n-p "Buffer must be in VC dired mode. Turn it on? "))
1889         (vc-dired-mode)
1890       (error "Aborted."))
1891     (dolist (file list)
1892       (setq fn          (file-name-nondirectory file)
1893             buffer      (get-file-buffer file)
1894             vc-reg-stat (vc-registered file)
1895             load        nil)
1896       (incf  count)
1897       ;; ... ... ... ... ... ... ... ... ... ... ... ... possible load . .
1898       (cond
1899        ((and (null buffer)
1900              vc-reg-stat
1901              (file-writable-p file)  ; "-r--r--r--" , not ci'able file
1902              (y-or-n-p (concat "file " fn " not in Emacs. Load? " )))
1903         (setq buffer (find-file-noselect file)
1904               load   t)
1905         (incf  loaded)))
1906       ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ...  stat . .
1907       (cond
1908        ((setq buffer (get-file-buffer file))
1909         (save-excursion
1910           (set-buffer buffer)
1911           (setq modify-stat (buffer-modified-p)
1912                 read-stat   buffer-read-only)
1913           ;;  Can't ask stat if not in VC control
1914           (and vc-reg-stat
1915                (setq diff-no-stat
1916                      (vc-workfile-unchanged-p file 'get-diffs))))))
1917       ;; ... ... ... ... ... ... ... ... ... ... ... ... set diff stat . .
1918       (cond
1919        ((and buffer
1920              (null vc-reg-stat))
1921         nil)
1922        ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1923        ((and buffer
1924              read-stat)                 ;is file in CheckOut state ?
1925         (ti::read-char-safe-until
1926          (concat fn " in VC, but _buffer_ is read-only. (ok)" ))
1927         (and load
1928              (y-or-n-p (concat "Unload " fn " ? "))
1929              (kill-buffer buffer)
1930              (decf loaded)))
1931        ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
1932        ((and buffer
1933              vc-reg-stat
1934              diff-no-stat
1935              (null modify-stat))
1936         (if (null load)
1937             (ti::read-char-safe-until
1938              (concat fn " contains NO changes. (ok) "))
1939           (if (y-or-n-p (concat fn " contains NO changes, unload NOW? "))
1940               (kill-buffer buffer))))
1941        ((and buffer
1942              vc-reg-stat)
1943         (incf  handled)
1944         (save-excursion
1945           (save-window-excursion
1946             (unwind-protect
1947                 (progn
1948                   (tinydired-mark-re (concat "[0-9] +" file) 'unmark)
1949                   (pop-to-buffer buffer)
1950                   (call-interactively 'vc-next-action)
1951
1952                   (ad-enable-advice 'vc-finish-logentry
1953                                     'after 'tinydired-recursive-edit)
1954                   (ad-activate 'vc-finish-logentry)
1955                   (recursive-edit)
1956                   (message
1957                    (substitute-command-keys
1958                     (concat
1959                      "Use \\[exit-recursive-edit] to abort action."
1960                      "to next file")))
1961                   (sleep-for 1))
1962               (ad-disable-advice  'vc-finish-logentry
1963                                   'after 'tinydired-recursive-edit)
1964               (ad-activate 'vc-finish-logentry))))
1965         (ti::pmin)                      ;remove file after VC
1966         (if (re-search-forward fn nil t)
1967             (dired-unmark 1))))
1968       ;; ........................................................ loop ...
1969       nil)
1970     (if verb
1971         (message (format "VC ci: %s/%s handled, loaded %s"
1972                          handled count loaded)))))
1973
1974 ;;}}}
1975 ;;{{{ code: advice
1976
1977 ;;; ----------------------------------------------------------------------
1978 ;;; - Until someone fixes dired to honor the  backup-file-name-p
1979 ;;;   this stays replaced...
1980 ;;; - This is copy from 19.30 dired.el
1981 ;;;
1982 (defadvice dired-flag-backup-files (around tdd dis)
1983   "Replace original function.
1984 This function honours the `backup-file-name-p' function and
1985 additionally flag files that match regexp `tinydired-:backup-file-regexp'."
1986   (let ((dired-marker-char      (if unflag-p ?\040 dired-del-marker))
1987         (re                     tinydired-:backup-file-regexp)
1988         file)
1989     (dired-mark-if
1990      (progn
1991        (beginning-of-line)
1992        (when (not (looking-at dired-re-dir))
1993          (setq file  (dired-get-filename t t))
1994          (if (stringp file)
1995              (or (backup-file-name-p file)
1996                  (and re
1997                       (string-match re file))))))
1998      "backup file")))
1999
2000 ;;; ----------------------------------------------------------------------
2001 ;;;
2002 (defadvice vc-finish-logentry (after tinydired-recursive-edit dis)
2003   "When this advice is enabled, it call `exit-recursive-edit'.
2004 Only if f recursive edit is in effect.
2005
2006 This advice is controlled by function `tinydired-marked-vc-ci' and it is never
2007 enabled outside of that function."
2008   (ignore-errors (exit-recursive-edit)))
2009
2010 ;;; ----------------------------------------------------------------------
2011 ;;; - When using "f" it loads directory to same buffer.
2012 ;;; - only kills the Dired buffer if a prefix arg is given
2013 ;;;
2014 (defadvice dired-find-file (around tinydired-kill-dired-buffer last dis)
2015   "If a prefix argument is given, kill the Dired buffer.
2016
2017 If you have loaded dired-x and it contains variable
2018 `dired-find-subdir', this advice does nothing."
2019   (let* ((dired-buffer (current-buffer)))
2020     (prog1
2021         ad-do-it
2022       (if (and (eq major-mode 'dired-mode)
2023                (not (eq (current-buffer) dired-buffer))
2024                (or current-prefix-arg
2025                    tinydired-:use-only-one-buffer-flag)
2026                (or (not (featurep 'dired-x)) ;not loaded
2027                    (and (featurep 'dired-x) ;is loaded, but this var not exist
2028                         (not (boundp 'dired-find-subdir)))))
2029           (kill-buffer dired-buffer)))))
2030
2031 ;;}}}
2032 ;;{{{ code: store
2033
2034 ;;; ----------------------------------------------------------------------
2035 ;;;
2036 (defsubst tinydired-store-get-string ()
2037   "Return content of storage as string."
2038   (ti::list-to-string tinydired-:file-store))
2039
2040 ;;; ----------------------------------------------------------------------
2041 ;;;
2042 (defun tinydired-store-show ()
2043   "Show filenames in storage."
2044   (interactive)
2045   (if (null tinydired-:file-store)
2046       (message "Tinydired: Store is empty.")
2047     (funcall tinydired-:show-storage-function tinydired-:file-store)))
2048
2049 ;;; ----------------------------------------------------------------------
2050 ;;;
2051 (defun tinydired-store-filename ()
2052   "Save current filename into variable."
2053   (interactive)
2054   (let* ((file (tinydired-get-filename)))
2055     (if (member file tinydired-:file-store)
2056         (message "TinyDireds: %s already in storage." file)
2057       (push  file tinydired-:file-store) file)
2058     (if (interactive-p)
2059         (tinydired-store-show))))
2060
2061 ;;; ----------------------------------------------------------------------
2062 ;;;
2063 (defun tinydired-store-delete-filename ()
2064   "Remove filename from store."
2065   (interactive)
2066   (let* ((file (tinydired-get-filename)))
2067     (setq tinydired-:file-store (delete file tinydired-:file-store))
2068     (if (interactive-p)
2069         (message "Tinydired: %s" (ti::list-to-string tinydired-:file-store)))))
2070
2071 ;;; ----------------------------------------------------------------------
2072 ;;;
2073 (defun tinydired-store-clear ()
2074   "Clear variable holding files."
2075   (interactive)
2076   (setq tinydired-:file-store nil)
2077   (if (interactive-p)
2078       (message "Tinydired: Storage cleared.")))
2079
2080 ;;; ----------------------------------------------------------------------
2081 ;;;
2082 (defun tinydired-store-remove-file ()
2083   "Delete current filename from storage."
2084   (interactive)
2085   (let* ((file   (tinydired-get-filename))
2086          (verb   (interactive-p))
2087          (store  tinydired-:file-store)
2088          list)
2089     (if (null store)
2090         (if verb (message "Tinydired: Storage is empty."))
2091       (dolist (x store)
2092         (if (not (string= x file))
2093             (push x list)) )
2094       (setq tinydired-:file-store list)
2095       (if verb
2096           (tinydired-store-show)))))
2097
2098 ;;; ----------------------------------------------------------------------
2099 ;;;
2100 (defun tinydired-store-delete-marked ()
2101   "Delete marked files from store."
2102   (interactive)
2103   (tinydired-store-add-marked 'delete (interactive-p)))
2104
2105 ;;; ----------------------------------------------------------------------
2106 ;;;
2107 (defun tinydired-store-add-marked (&optional delete verb)
2108   "Add marked files into store. No duplicates are inserted.
2109 If parameter DELETE is non-nil, removes marked files from store. VERB."
2110   (interactive)
2111   (let* ((list    tinydired-:file-store)
2112          (marked  (tinydired-get-marked-files-no-dir)))
2113     (ti::verb)
2114     (if (null delete)
2115         (dolist (x marked)
2116           (if (not (member x list))
2117               (push x tinydired-:file-store)))
2118       (dolist (x marked)
2119         (if (member x list)
2120             (setq tinydired-:file-store
2121                   (delete x tinydired-:file-store)))))
2122     (if verb
2123         (tinydired-store-show))))
2124
2125 ;;}}}
2126 ;;{{{ code: ange ftp
2127
2128 ;;; ----------------------------------------------------------------------
2129 ;;;
2130 (defun tinydired-store-ftp-message (&rest args)
2131   "Show Message from ange ftp after finishing the mget. Ange ARGS."
2132   (message "Tinydired: Store, ftp completed.") (sleep-for 1))
2133
2134 ;;; ----------------------------------------------------------------------
2135 ;;;
2136 (defun tinydired-back-to-dired-buffer ()
2137   "Switch back to dired buffer, which is associated with ange-ftp buffer.
2138 If no such buffer is found, do nothing."
2139   (interactive)
2140   (let* ((buffer (ti::buffer-find-ange-to-dired-buffer)))
2141     (if buffer
2142         (pop-to-buffer (car buffer))
2143       (message "Tinydired: No dired buffer found."))))
2144
2145 ;;; ----------------------------------------------------------------------
2146 ;;; - If I have 2-3 dired ftp sessions and I want to close the current
2147 ;;;   one, this is a handy command.
2148 ;;;
2149 ;;;###autoload
2150 (defun tinydired-kill-dired-and-ange-session (&optional verb)
2151   "Kill the current dired buffer and possible ange-ftp buffer. VERB.
2152 This is like `dired-delete-and-exit'."
2153   (interactive)
2154   (let* ((buffer  (tinydired-ange-ftp-buffer-for-this-dired)))
2155     (ti::verb)
2156     (if buffer
2157         (kill-buffer buffer))
2158     (kill-buffer (current-buffer))
2159     (if verb
2160         (message
2161          (if buffer
2162              "Ange buffer killed too."
2163            "No ange buffer associated with dired.")))))
2164
2165 ;;; ----------------------------------------------------------------------
2166 ;;;
2167 ;;;###autoload
2168 (defun tinydired-kill-all-ange-and-dired-buffers (&optional verb)
2169   "Kill all ange-ftp buffers _and_ all remote dired buffers. VERB."
2170   (interactive)
2171   (let* ((ange  (ti::buffer-get-ange-buffer-list))
2172          (dired (ti::dolist-buffer-list
2173                  (and (eq major-mode 'dired-mode)
2174                       (string-match tinydired-:dired-directory-ange-regexp
2175                                     dired-directory))))
2176          (ange-count  0)
2177          (dired-count 0))
2178     (ti::verb)
2179     (dolist (elt ange)
2180       (kill-buffer elt)
2181       (incf  ange-count))
2182     (dolist (elt dired)
2183       (kill-buffer elt)
2184       (incf  dired-count))
2185     (if verb
2186         (message "Tinydired: Killed %s ange, %s dired buffers."
2187                  ange-count dired-count))))
2188
2189 ;;; ----------------------------------------------------------------------
2190 ;;;
2191 ;;;###autoload
2192 (defun tinydired-kill-all-ange-buffers ()
2193   "Kill all ange-ftp process buffers.
2194 If you want to kill one buffer at a time, use
2195 `tinydired-switch-to-some-ange-ftp-buffer' to switch to individual buffer
2196 and use \\[kill-buffer] to kill session.
2197
2198 This function is primarily used for cleanups. After a while
2199 you may end up with many ftp session and it's nice if
2200 you can get rid of them fast.
2201
2202 Don't worry about the dired buffers, Ange will automatically
2203 create connection, if you use \"g\" -- rever-buffer, in a dired
2204 that is associated with ange-ftp."
2205   (interactive)
2206   (let* ((list  (ti::buffer-get-ange-buffer-list))
2207          (i     0))
2208     (dolist (elt list)
2209       (incf  i) (kill-buffer elt))
2210     (if (> i 0 )
2211         (message (concat "Tinydired: Ange buffers killed: " i))
2212       (message "Tinydired: No ange buffers found."))))
2213
2214 ;;; ----------------------------------------------------------------------
2215 ;;;
2216 ;;;###autoload
2217 (defun tinydired-switch-to-some-ange-ftp-buffer ()
2218   "Gather all ange FTP buffers and offer completion menu.
2219 If there is only one Ange buffer, switches to it without asking."
2220   (interactive)
2221   (let* ((list  (ti::buffer-get-ange-buffer-list))
2222          buffer
2223          go)
2224     (if (null list)
2225         (message "no Ange-ftp sessions at the moment.")
2226       (if (eq 1 (length list))
2227           (setq buffer (car list))
2228         (setq buffer
2229               (completing-read "go ange: " (ti::list-to-assoc-menu  list))))
2230       (if (setq go (get-buffer buffer))
2231           (switch-to-buffer go)
2232         (message (concat "No ange buffer: " buffer))))))
2233
2234 ;;; ----------------------------------------------------------------------
2235 ;;; - This is handy, when you want to check that the mput went ok.
2236 ;;;
2237 (defun tinydired-switch-to-mput-ange-ftp-buffer ()
2238   "Switch to ange buffer where last mput was made.
2239 Does nothing if no mput were recorded or such ange buffer does not exist.
2240
2241 Binds local keys to ftp buffer
2242
2243   C - c b               switch back to previous buffer
2244
2245 References:
2246
2247   `tinydired-:previous-buffer'
2248   `tinydired-:mput-last-ftp'
2249
2250 Return
2251
2252   nil                   no action taken.
2253   t"
2254   (interactive)
2255   (let* ((file    tinydired-:mput-last-ftp)
2256          (buffer  (current-buffer))
2257          list
2258          host
2259          ret)
2260     (cond
2261      ((null tinydired-:mput-last-ftp)
2262       (message "Tinydired: Sorry, No mput information."))
2263      ((not (string-match "/.*@.*:" file))
2264       (message "Tinydired: Sorry, No ange reference in `tinydired-:mput-last-ft'p"))
2265      (t
2266       ;;  This return 3 member list: SITE LOGIN DIRECTORY/FILE
2267       (setq list  (ange-ftp-ftp-name file)
2268             host  (nth 0 list))
2269       (setq tinydired-:previous-buffer buffer)
2270       ;;  Try to find buffer , ange uses SITE name for buffer names
2271       ;;  *ftp omc@venus*
2272       (cond
2273        ((and list
2274              (setq buffer (car (ti::dolist-buffer-list
2275                                 (string-match
2276                                  (concat "[*]ftp.*" (regexp-quote host))
2277                                  (buffer-name))
2278                                 'temp-buffers))))
2279         (switch-to-buffer-other-window buffer)
2280         (ti::pmax)
2281         ;; Switching back to previous (b)uffer
2282         (local-set-key "\C-cb"
2283                        (function
2284                         (lambda ()
2285                           "TinyDired: mput ange, back to previous buffer"
2286                           (interactive)
2287                           (pop-to-buffer tinydired-:previous-buffer))))
2288         (setq ret t))
2289        (t
2290         (message
2291          "Tinydired: Sorry, can't find ange buffer for `%s'" host)))))
2292     ret))
2293
2294 ;;; ----------------------------------------------------------------------
2295 ;;;
2296 (defun tinydired-ange-ftp-buffer-for-this-dired (&optional file)
2297   "Return ange ftp buffer-name-string for current dired or FILE, or nil."
2298   (let* (host
2299          buffer
2300          list)
2301     (setq file (or file (dired-get-filename)))
2302     ;;  This return 3 member list: SITE LOGIN DIRECTORY/FILE
2303     (setq list  (ange-ftp-ftp-name file)
2304           host  (nth 0 list))
2305     (when list                       ;This dired is not in remote site
2306       ;;  Remove that ange-ftp site information from the string.
2307       (setq tinydired-:directory
2308             (ti::string-index-substring dired-directory ?: nil 'right))
2309       ;;  Try to find buffer , ange uses SITE name for buffer names
2310       ;;  *ftp omc@venus*
2311       (when list
2312         (setq buffer
2313               (car (ti::dolist-buffer-list
2314                     (string-match
2315                      (concat "[*]ftp.*" (regexp-quote host))
2316                      (buffer-name))
2317                     'temp-buffers)))
2318         (unless (get-buffer buffer)
2319           (setq buffer nil))))
2320     buffer))
2321
2322 ;;; ----------------------------------------------------------------------
2323 ;;;
2324 (defun tinydired-switch-to-ange-ftp-buffer (&optional verb)
2325   "If the dired is ange ftp buffer, switch to the real ftp buffer. VERB.
2326
2327 Sets global
2328  `tinydired-:directory'   filename for current line
2329
2330 Binds local keys in ftp buffer
2331
2332  C - c af           insert files stored in current point
2333  C - c ad           insert directory name
2334  C - c ab           switch back to dired buffer"
2335   (interactive)
2336   (let* (buffer
2337          dir)
2338     (ti::verb)
2339     ;;  1.  try normal ange ftp
2340     ;;  2.  did user used 'put' to remove site ?
2341     ;;
2342     (setq buffer (tinydired-ange-ftp-buffer-for-this-dired))
2343     (cond
2344      ((and (null buffer)
2345            tinydired-:file-store
2346            tinydired-:mput-last-ftp
2347            (null (tinydired-switch-to-mput-ange-ftp-buffer)))
2348       (if verb
2349           (message "Tinydired: can't locate associated ftp buffer.")))
2350      ((null buffer)
2351       (if verb
2352           (message "Tinydired: can't locate associated ftp buffer.")))
2353      (buffer
2354       (switch-to-buffer-other-window buffer)
2355       (set (make-local-variable 'tinydired-:directory) dir)
2356       (ti::pmax)
2357       ;;  "f"  for file information
2358       (local-set-key
2359        "\C-caf"
2360        (function
2361         (lambda (&optional arg)
2362           "TinyDired: Inserts file storage string."
2363           (interactive "P")
2364           (setq arg (tinydired-store-get-string))
2365           (if (ti::nil-p arg)
2366               (message "Tinydired: No files in storage.")
2367             (insert (tinydired-store-get-string))))))
2368       ;; "d" for directory information
2369       (local-set-key
2370        "\C-cad"
2371        (function
2372         (lambda ()
2373           "TinyDired: Inserts dired's directory string."
2374           (interactive)
2375           (insert tinydired-:directory))))
2376       ;; Switching back to dired (b)uffer
2377       (local-set-key
2378        "\C-cab"
2379        (function
2380         (lambda ()
2381           "TinyDired: Back to dired buffer"
2382           (interactive)
2383           (tinydired-back-to-dired-buffer))))))))
2384
2385 ;;; ----------------------------------------------------------------------
2386 ;;; mget = multiple get
2387 ;;;
2388 (defun tinydired-store-ftp-mget ()
2389   "Send command to ange to fetch all files in store."
2390   (interactive)
2391   (let* ((files         tinydired-:file-store)
2392          (down          tinydired-:download-dir)
2393          (store         (tinydired-store-get-string))
2394          (ange          (ange-ftp-ftp-name dired-directory))
2395          to-dir
2396          host
2397          user
2398          dir)
2399     (cond
2400      ((null ange)
2401       (message "Tinydired: Can't find ftp process. Start one first."))
2402      ((ti::nil-p files)
2403       (message "Tinydired: No files in store."))
2404      (t
2405       (if (or (not
2406                (y-or-n-p (concat "Tinydired: really get: "
2407                                  ;;   Get nicer prompt
2408                                  (if (> (length store) 50)
2409                                      (concat (substring store 0 50 )
2410                                              "...")
2411                                    (concat store " ")))))
2412               (ti::nil-p
2413                (setq to-dir
2414                      ;;  Hack to read directory easily
2415                      (let ((default-directory down))
2416                        (call-interactively
2417                         (function
2418                          (lambda (dir)
2419                            (interactive "Ddownload dir: ")
2420                            dir)))))))
2421           (message "Tinydired: Cancelled.")
2422         ;; ................................................. then part ...
2423         ;; - First update the value, so that user gets the old selection
2424         (setq tinydired-:download-dir to-dir)
2425         ;; Next, get all needed parameters
2426         (setq host  (nth 0 ange)
2427               user  (nth 1 ange)
2428               dir   (nth 2 ange)
2429               to-dir (expand-file-name to-dir))
2430         (ti::file-ange-file-handle
2431          'get user host dir to-dir files))))))
2432
2433 ;;; ----------------------------------------------------------------------
2434 ;;; - remember to be in DIRED before you call this
2435 ;;; mput = multiple put
2436 ;;;
2437 (defun tinydired-store-ftp-mput (ange-ref-to)
2438   "Send all files in store to remote site ANGE-REF-TO."
2439   (interactive
2440    (list
2441     (completing-read
2442      "mput site: "
2443      (ti::list-to-assoc-menu tinydired-:mput-sites)
2444      nil nil tinydired-:mput-last-ftp
2445      'tinydired-:mput-history)))
2446   (if (null dired-directory)
2447       (error "Tinydired: Must execute command in dired buffer."))
2448   ;;    Record the site name where the mput was made
2449   (setq tinydired-:mput-last-ftp ange-ref-to)
2450   (let* ((files         tinydired-:file-store)
2451          (store         (tinydired-store-get-string))
2452          (dir           dired-directory)
2453          ange
2454          to-dir
2455          host
2456          user)
2457     ;;  If user is in remote dired buffer, signal error
2458     ;;  We don't support this. At least not now.
2459     ;;
2460     (if (string-match "@" dired-directory)
2461         (error "Tinydired: sorry, load files first to your site."))
2462     (if (not (ti::nil-p ange-ref-to))
2463         (setq ange   (ange-ftp-ftp-name ange-ref-to) ;crack it
2464               host   (nth 0 ange)
2465               user   (nth 1 ange)
2466               to-dir (nth 2 ange)))
2467     (cond
2468      ((ti::nil-p ange-ref-to)
2469       (message "Tinydired: No site given"))
2470      ((ti::nil-p files)
2471       (message "Tinydired: No files in store."))
2472      ((ti::nil-p to-dir)
2473       (message "Tinydired: No destination download directory given"))
2474      (t
2475       (if (not (y-or-n-p (concat "Put " host ": "
2476                                  ;;   Get nicer prompt
2477                                  (if (> (length store) 50)
2478                                      (concat (substring store 0 50)
2479                                              "...")
2480                                    (concat store " ")))))
2481           (message "Tinydired: Cancelled.")
2482         ;; ................................................. then part ...
2483         ;; (mode user host dir lcd file-list &optional not-bg msg-func)
2484         (ti::file-ange-file-handle 'put user host to-dir dir files))))))
2485
2486 ;;}}}
2487
2488 (provide   'tinydired)
2489
2490 (tinydired-install)
2491 (run-hooks 'tinydired-:load-hook)
2492
2493 ;;; tinydired.el ends here