diff options
| author | Richard M. Stallman | 1993-11-23 07:00:40 +0000 |
|---|---|---|
| committer | Richard M. Stallman | 1993-11-23 07:00:40 +0000 |
| commit | bb5d4e1a381cbf632b05f6fc5b2415d70ec9c68e (patch) | |
| tree | e073990330d2505c3614ec49f28d9645ed6124c1 | |
| parent | acb93e31f5a88e2461b1fa283bd341b4f66d599b (diff) | |
| download | emacs-bb5d4e1a381cbf632b05f6fc5b2415d70ec9c68e.tar.gz emacs-bb5d4e1a381cbf632b05f6fc5b2415d70ec9c68e.zip | |
Initial revision
| -rw-r--r-- | etc/tasks.texi | 440 | ||||
| -rw-r--r-- | lisp/avoid.el | 254 | ||||
| -rw-r--r-- | lisp/shadowfile.el | 830 |
3 files changed, 1524 insertions, 0 deletions
diff --git a/etc/tasks.texi b/etc/tasks.texi new file mode 100644 index 00000000000..d408203a1f6 --- /dev/null +++ b/etc/tasks.texi | |||
| @@ -0,0 +1,440 @@ | |||
| 1 | \input texinfo @c -*-texinfo-*- | ||
| 2 | @c %**start of header | ||
| 3 | @setfilename tasks.info | ||
| 4 | @settitle GNU Task List | ||
| 5 | @c UPDATE THIS DATE WHENEVER YOU MAKE CHANGES! | ||
| 6 | @set lastupdate 16 July 1993 | ||
| 7 | @c %**end of header | ||
| 8 | |||
| 9 | @setchapternewpage off | ||
| 10 | |||
| 11 | @ifinfo | ||
| 12 | Copyright (C) 1993 Free Software Foundation | ||
| 13 | |||
| 14 | Permission is granted to make and distribute verbatim copies of | ||
| 15 | this manual provided the copyright notice and this permission notice | ||
| 16 | are preserved on all copies. | ||
| 17 | |||
| 18 | @ignore | ||
| 19 | Permission is granted to process this file through TeX and print the | ||
| 20 | results, provided the printed document carries copying permission | ||
| 21 | notice identical to this one except for the removal of this paragraph | ||
| 22 | (this paragraph not being relevant to the printed manual). | ||
| 23 | @end ignore | ||
| 24 | |||
| 25 | Permission is granted to copy and distribute modified versions of this | ||
| 26 | manual under the conditions for verbatim copying, provided that the entire | ||
| 27 | resulting derived work is distributed under the terms of a permission | ||
| 28 | notice identical to this one. | ||
| 29 | |||
| 30 | Permission is granted to copy and distribute translations of this manual | ||
| 31 | into another language, under the above conditions for modified versions, | ||
| 32 | except that this permission notice may be stated in a translation approved | ||
| 33 | by the Free Software Foundation. | ||
| 34 | @end ifinfo | ||
| 35 | |||
| 36 | @titlepage | ||
| 37 | @title GNU Task List | ||
| 38 | @author Richard Stallman | ||
| 39 | @author last updated @value{lastupdate} | ||
| 40 | @page | ||
| 41 | |||
| 42 | @vskip 0pt plus 1filll | ||
| 43 | Copyright @copyright{} 1993 Free Software Foundation | ||
| 44 | |||
| 45 | Permission is granted to make and distribute verbatim copies of | ||
| 46 | this manual provided the copyright notice and this permission notice | ||
| 47 | are preserved on all copies. | ||
| 48 | |||
| 49 | Permission is granted to copy and distribute modified versions of this | ||
| 50 | manual under the conditions for verbatim copying, provided that the entire | ||
| 51 | resulting derived work is distributed under the terms of a permission | ||
| 52 | notice identical to this one. | ||
| 53 | |||
| 54 | Permission is granted to copy and distribute translations of this manual | ||
| 55 | into another language, under the above conditions for modified versions, | ||
| 56 | except that this permission notice may be stated in a translation approved | ||
| 57 | by Free Software Foundation. | ||
| 58 | @end titlepage | ||
| 59 | |||
| 60 | @ifinfo | ||
| 61 | @node Top, Documentation, (dir), (dir) | ||
| 62 | @top Version | ||
| 63 | |||
| 64 | Last updated @value{lastupdate}. | ||
| 65 | @end ifinfo | ||
| 66 | |||
| 67 | Check with gnu@@prep.ai.mit.edu, for a possibly more current copy. | ||
| 68 | This task list is not exclusive; any other useful program might be a | ||
| 69 | good project--but it might instead be something we already have, so | ||
| 70 | check with gnu@@prep before you start writing it. | ||
| 71 | |||
| 72 | @menu | ||
| 73 | * Documentation:: | ||
| 74 | * Unix-related projects:: | ||
| 75 | * Kernel-related projects:: | ||
| 76 | * Extensions to existing GNU software:: | ||
| 77 | * X windows projects:: | ||
| 78 | * Other random projects:: | ||
| 79 | * Compilers for other batch languages:: | ||
| 80 | * Games and recreations:: | ||
| 81 | @end menu | ||
| 82 | |||
| 83 | If you start working steadily on a project, please let gnu@@prep know. | ||
| 84 | We might have information that could help you; we'd also like to send | ||
| 85 | you the GNU coding standards. | ||
| 86 | |||
| 87 | Because of the natural tendency for most volunteers to write | ||
| 88 | programming tools or programming languages, we have a comparative | ||
| 89 | shortage of applications useful for non-programmer users. Therefore, | ||
| 90 | we ask you to consider writing such a program. | ||
| 91 | |||
| 92 | In general, a new program that does a completely new job advances the | ||
| 93 | GNU project more than an improvement to an existing program. | ||
| 94 | |||
| 95 | @node Documentation, Unix-related projects, Top, Top | ||
| 96 | @chapter Documentation | ||
| 97 | |||
| 98 | We very urgently need documentation for some parts of the system | ||
| 99 | that already exist or will exist very soon: | ||
| 100 | |||
| 101 | @itemize @bullet | ||
| 102 | |||
| 103 | @item | ||
| 104 | A C reference manual. (RMS has written half of one which you could | ||
| 105 | start with). | ||
| 106 | |||
| 107 | @item | ||
| 108 | A manual for Ghostscript. | ||
| 109 | |||
| 110 | @item | ||
| 111 | A manual for CSH. | ||
| 112 | |||
| 113 | @item | ||
| 114 | A manual for PIC (the graphics formatting language). | ||
| 115 | |||
| 116 | @item | ||
| 117 | A manual for Perl. (The manual that exists is not free, and | ||
| 118 | is thus not available to be part of the GNU system.) | ||
| 119 | |||
| 120 | @item | ||
| 121 | A manual for Oleo. | ||
| 122 | |||
| 123 | @item | ||
| 124 | A book on how GCC works and why various machine descriptions | ||
| 125 | are written as they are. | ||
| 126 | |||
| 127 | @item | ||
| 128 | A manual for programming X-window applications. | ||
| 129 | |||
| 130 | @item | ||
| 131 | Manuals for various X window managers. | ||
| 132 | |||
| 133 | @item | ||
| 134 | Reference cards for those manuals that don't have them: Gawk, C | ||
| 135 | Compiler, Make, Texinfo, Termcap and maybe the C Library. | ||
| 136 | |||
| 137 | @item | ||
| 138 | Many utilities need documentation, including @code{grep}, @code{cpio}, | ||
| 139 | @code{find}, @code{less}, and the other small utilities. | ||
| 140 | |||
| 141 | @end itemize | ||
| 142 | |||
| 143 | @node Unix-related projects, Kernel-related projects, Documentation, Top | ||
| 144 | @chapter Unix-related projects | ||
| 145 | |||
| 146 | @itemize | ||
| 147 | |||
| 148 | @item | ||
| 149 | We could use an emulation of Unix @code{spell}, which would run by | ||
| 150 | invoking @code{ispell}. | ||
| 151 | |||
| 152 | @item | ||
| 153 | Less urgent: @code{diction}, @code{explain}, @code{style}. | ||
| 154 | |||
| 155 | @item | ||
| 156 | An improved version of the POSIX utility @code{pax}. There is one on | ||
| 157 | the usenet, but it is said to be poorly written. Talk with | ||
| 158 | mib@@gnu.ai.mit.edu about this project. | ||
| 159 | |||
| 160 | @item | ||
| 161 | Modify the GNU @code{dc} program to use the math routines of GNU | ||
| 162 | @code{bc}. | ||
| 163 | |||
| 164 | @item | ||
| 165 | A @code{grap} preprocessor program for @code{troff}. | ||
| 166 | |||
| 167 | @item | ||
| 168 | Various other libraries. | ||
| 169 | |||
| 170 | @item | ||
| 171 | An emulation of SCCS that works using RCS. | ||
| 172 | |||
| 173 | @end itemize | ||
| 174 | |||
| 175 | @node Kernel-related projects, Extensions to existing GNU software, Unix-related projects, Top | ||
| 176 | @chapter Kernel-related projects | ||
| 177 | |||
| 178 | @itemize | ||
| 179 | |||
| 180 | @item | ||
| 181 | An over-the-ethernet debugger that will allow the kernel to be | ||
| 182 | debugged from GDB running on another machine. | ||
| 183 | |||
| 184 | @item | ||
| 185 | A shared memory X11 server to run under MACH is very desirable. The | ||
| 186 | machine specific parts should be kept well separated. | ||
| 187 | |||
| 188 | @end itemize | ||
| 189 | |||
| 190 | @node Extensions to existing GNU software, X windows projects, Kernel-related projects, Top | ||
| 191 | @chapter Extensions to existing GNU software | ||
| 192 | |||
| 193 | @itemize | ||
| 194 | |||
| 195 | @item | ||
| 196 | Enhance GCC. See files PROJECTS and PROBLEMS in the GCC distribution. | ||
| 197 | |||
| 198 | @item | ||
| 199 | GNU @code{sed} probably needs to be rewritten completely just to make it | ||
| 200 | cleaner. | ||
| 201 | |||
| 202 | @item | ||
| 203 | Add a few features to GNU @code{diff}, such as handling large input | ||
| 204 | files without reading entire files into core. | ||
| 205 | |||
| 206 | @item | ||
| 207 | Extend GDB with an X-based graphical interface better than @code{xxgdb}. | ||
| 208 | |||
| 209 | @item | ||
| 210 | An @code{nroff} macro package to simplify @code{texi2roff}. | ||
| 211 | |||
| 212 | @item | ||
| 213 | A queueing system for the mailer Smail that groups pending work by | ||
| 214 | destination rather than by original message. This makes it possible | ||
| 215 | to schedule retries coherently for each destination. Talk to | ||
| 216 | tron@@veritas.com about this. | ||
| 217 | |||
| 218 | @item | ||
| 219 | Cross-referencing, flow graph, and execution trace programs for C and | ||
| 220 | other languages, like @code{cxref}, @code{cflow}, and @code{ctrace}. | ||
| 221 | |||
| 222 | @end itemize | ||
| 223 | |||
| 224 | @node X windows projects, Other random projects, Extensions to existing GNU software, Top | ||
| 225 | @chapter X windows projects | ||
| 226 | |||
| 227 | @itemize | ||
| 228 | |||
| 229 | @item | ||
| 230 | An emulator for Macintosh graphics calls on top of X Windows. | ||
| 231 | |||
| 232 | @item | ||
| 233 | An emulator for Microsoft windows calls on top of X Windows. (A | ||
| 234 | commercial program to do this took just three months to write.) | ||
| 235 | |||
| 236 | @item | ||
| 237 | A music playing and editing system. | ||
| 238 | |||
| 239 | @item | ||
| 240 | A program to edit dance notation (such as labanotation) and display | ||
| 241 | dancers moving on the screen. | ||
| 242 | |||
| 243 | @item | ||
| 244 | A library for displaying circle-shaped menus with X windows. | ||
| 245 | |||
| 246 | @item | ||
| 247 | A program to display and edit Hypercard stacks. | ||
| 248 | |||
| 249 | @item | ||
| 250 | An interface-builder program to make it easy to design graphical | ||
| 251 | interfaces for applications. This could work with the dynamic linker | ||
| 252 | DLD and C++, loading in the same class definitions that will be used | ||
| 253 | by the application program. | ||
| 254 | |||
| 255 | @item | ||
| 256 | A desktop program with icons and such, for X-windows. | ||
| 257 | |||
| 258 | @item | ||
| 259 | A paint program, supporting both bitmap-oriented operations and | ||
| 260 | component-oriented operations. @code{xpaint} exists, but isn't very | ||
| 261 | usable. | ||
| 262 | |||
| 263 | @end itemize | ||
| 264 | |||
| 265 | @node Other random projects, Compilers for other batch languages, X windows projects, Top | ||
| 266 | @chapter Other random projects | ||
| 267 | |||
| 268 | If you think of others that should be added, please | ||
| 269 | send them to gnu@@prep.ai.mit.edu. | ||
| 270 | |||
| 271 | @itemize | ||
| 272 | |||
| 273 | @item | ||
| 274 | [This seems to be being done:] | ||
| 275 | A program to convert Postscript to plain ASCII text. Ghostscript will | ||
| 276 | soon have a mode to output all the text strings in a document, each with | ||
| 277 | its coordinates. You could write a program to start with this output | ||
| 278 | and ``layout the page'' in ASCII. The program will be both easier and | ||
| 279 | more useful if you don't worry pedantically about how the output text | ||
| 280 | should be formatted. Instead, try to make it look reasonable as plain | ||
| 281 | ASCII. | ||
| 282 | |||
| 283 | @item | ||
| 284 | A program to convert compiled programs represented in OSF ANDF | ||
| 285 | (``Architecture Neutral Distribution Format'') into ANSI C. | ||
| 286 | |||
| 287 | @item | ||
| 288 | An imitation of Page Maker or Ventura Publisher. | ||
| 289 | |||
| 290 | @item | ||
| 291 | An imitation of @code{dbase2} or @code{dbase3} (How dbased!) | ||
| 292 | |||
| 293 | @item | ||
| 294 | A program to reformat Fortran programs in a way that is pretty. | ||
| 295 | |||
| 296 | @item | ||
| 297 | A bulletin board system. There are a few free ones, but they don't have | ||
| 298 | all the features that people want in such systems. It would make sense | ||
| 299 | to start with an existing one and add the other features. | ||
| 300 | |||
| 301 | @item | ||
| 302 | A general ledger program. | ||
| 303 | |||
| 304 | @item | ||
| 305 | A single command language that could be suitable for use in a shell, in | ||
| 306 | GDB for programming debugging commands, in a program like @code{awk}, in | ||
| 307 | a calculator like @code{bc}, and so on. The fact that all these | ||
| 308 | programs are similar but different in peculiar details is a great source | ||
| 309 | of confusion. We are stuck with maintaining compatibility with Unix in | ||
| 310 | our shell, @code{awk}, and @code{bc}, but nothing prevents us from | ||
| 311 | having alternative programs using our new, uniform language. This would | ||
| 312 | make GNU far better for new users. | ||
| 313 | |||
| 314 | @item | ||
| 315 | A program to typeset C code for printing. | ||
| 316 | For ideas on what to do, see the forthcoming book, | ||
| 317 | |||
| 318 | @display | ||
| 319 | Human Factors and Typography for More Readable Programs, | ||
| 320 | Ronald M. Baecker and Aaron Marcus, | ||
| 321 | Addison-Wesley, ISBN 0-201-10745-7 | ||
| 322 | @end display | ||
| 323 | |||
| 324 | (I don't quite agree with a few of the details they propose.) | ||
| 325 | |||
| 326 | @item | ||
| 327 | Speech-generation programs (there is a program from Brown U that you | ||
| 328 | could improve). | ||
| 329 | |||
| 330 | @item | ||
| 331 | Speech-recognition programs (single-speaker, disconnected speech). | ||
| 332 | |||
| 333 | @item | ||
| 334 | Scientific mathematical subroutines, including clones of SPSS. | ||
| 335 | |||
| 336 | @item | ||
| 337 | Statistical tools. | ||
| 338 | |||
| 339 | @item | ||
| 340 | Software to replace card catalogues in libraries. | ||
| 341 | |||
| 342 | @item | ||
| 343 | Grammar and style checking programs. | ||
| 344 | |||
| 345 | @item | ||
| 346 | An implementation of the S language. | ||
| 347 | |||
| 348 | @item | ||
| 349 | A translator from Scheme to C. | ||
| 350 | |||
| 351 | @item | ||
| 352 | Optical character recognition programs; especially if suitable for | ||
| 353 | scanning documents with multiple fonts and capturing font info as well | ||
| 354 | as character codes. This may not be very difficult if you let it | ||
| 355 | @emph{train} on part of the individual document to be scanned, so as to | ||
| 356 | learn what fonts are in use in that document. We would particularly | ||
| 357 | like to scan the Century Dictionary, an unabridged dictionary now in the | ||
| 358 | public domain. | ||
| 359 | |||
| 360 | You don't need scanning hardware to work on OCR. We can send you | ||
| 361 | bitmaps you can use as test data. | ||
| 362 | |||
| 363 | @item | ||
| 364 | A program to scan a line drawing and convert it to Postscript. | ||
| 365 | |||
| 366 | @item | ||
| 367 | A program to recognize handwriting. | ||
| 368 | |||
| 369 | @item | ||
| 370 | A pen based interface. | ||
| 371 | |||
| 372 | @item | ||
| 373 | Software suitable for creating virtual reality user interfaces. | ||
| 374 | |||
| 375 | @item | ||
| 376 | CAD software, such as a vague imitation of Autocad. | ||
| 377 | |||
| 378 | @item | ||
| 379 | Software for displaying molecules. | ||
| 380 | |||
| 381 | @item | ||
| 382 | Software for comparing DNA sequences, and finding matches and | ||
| 383 | alignments. | ||
| 384 | |||
| 385 | @end itemize | ||
| 386 | |||
| 387 | @node Compilers for other batch languages, Games and recreations, Other random projects, Top | ||
| 388 | @chapter Compilers for other batch languages | ||
| 389 | |||
| 390 | Volunteers are needed to write parsers/front ends for languages such | ||
| 391 | as Algol 60, Algol 68, PL/I, or whatever, to be used with the | ||
| 392 | code generation phases of the GNU C compiler. (C++ is done, and | ||
| 393 | Ada, Fortran, Pascal and Modula are being worked on.) | ||
| 394 | |||
| 395 | @node Games and recreations, , Compilers for other batch languages, Top | ||
| 396 | @chapter Games and recreations | ||
| 397 | |||
| 398 | @itemize | ||
| 399 | |||
| 400 | @item | ||
| 401 | Video-oriented games should work with the X window system. | ||
| 402 | |||
| 403 | @item | ||
| 404 | Empire (there is a free version but it needs upgrading) | ||
| 405 | |||
| 406 | @item | ||
| 407 | Imitations of popular video games: | ||
| 408 | |||
| 409 | @itemize | ||
| 410 | @item | ||
| 411 | Space war, Asteroids, Pong, Columns. | ||
| 412 | @item | ||
| 413 | Defending cities from missiles. | ||
| 414 | @item | ||
| 415 | Plane shoots at lots of other planes. | ||
| 416 | @item | ||
| 417 | Wizard fights fanciful monster. | ||
| 418 | @item | ||
| 419 | A golf game. | ||
| 420 | @item | ||
| 421 | Program a robot by sticking building blocks together, | ||
| 422 | then watch it explore a world. | ||
| 423 | @item | ||
| 424 | Biomorph evolution (as in Scientific American). | ||
| 425 | @item | ||
| 426 | A program to display effects of moving at relativistic speeds. | ||
| 427 | @end itemize | ||
| 428 | |||
| 429 | @item | ||
| 430 | Intriguing screen-saver programs to make interesting pictures. | ||
| 431 | Other such programs that are simply entertaining to watch. | ||
| 432 | For example, an aquarium. | ||
| 433 | |||
| 434 | @end itemize | ||
| 435 | |||
| 436 | We do not need @code{rogue}, as we have @code{hack}. | ||
| 437 | |||
| 438 | @contents | ||
| 439 | |||
| 440 | @bye | ||
diff --git a/lisp/avoid.el b/lisp/avoid.el new file mode 100644 index 00000000000..664c964806a --- /dev/null +++ b/lisp/avoid.el | |||
| @@ -0,0 +1,254 @@ | |||
| 1 | ;;; avoid.el -- make mouse pointer stay out of the way of editing. | ||
| 2 | |||
| 3 | ;;; Copyright (C) 1993 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Boris Goldowsky <boris@cs.rochester.edu> | ||
| 6 | ;; Keywords: mouse | ||
| 7 | ;; $Revision: 1.10 $ | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software; you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation; either version 2, or (at your option) | ||
| 14 | ;; any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs; see the file COPYING. If not, write to | ||
| 23 | ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 24 | |||
| 25 | ;;; Commentary: | ||
| 26 | ;;; | ||
| 27 | ;;; For those who are annoyed by the mouse pointer obscuring text, | ||
| 28 | ;;; this mode moves the mouse pointer - either just a little out of | ||
| 29 | ;;; the way, or all the way to the corner of the frame. | ||
| 30 | ;;; To use, load or evaluate this file and type M-x mouse-avoidance-mode . | ||
| 31 | ;;; To set up permanently, put this file on your load-path and put the | ||
| 32 | ;;; following in your .emacs: | ||
| 33 | ;;; | ||
| 34 | ;;; (cond (window-system | ||
| 35 | ;;; (require 'avoid) | ||
| 36 | ;;; (mouse-avoidance-mode 'cat-and-mouse))) | ||
| 37 | ;;; | ||
| 38 | ;;; The 'animate can be 'jump or 'banish or 'protean if you prefer. | ||
| 39 | ;;; | ||
| 40 | ;;; For added silliness, make the animatee animate... | ||
| 41 | ;;; put something similar to the following into your .emacs: | ||
| 42 | ;;; | ||
| 43 | ;;; (cond (window-system | ||
| 44 | ;;; (setq x-pointer-shape | ||
| 45 | ;;; (eval (nth (random 4) | ||
| 46 | ;;; '(x-pointer-man x-pointer-spider | ||
| 47 | ;;; x-pointer-gobbler x-pointer-gumby)))) | ||
| 48 | ;;; (set-mouse-color (cdr (assoc 'mouse-color (frame-parameters)))))) | ||
| 49 | ;;; | ||
| 50 | ;;; For completely random pointer shape, replace the setq above with: | ||
| 51 | ;;; (setq x-pointer-shape (mouse-avoidance-random-shape)) | ||
| 52 | ;;; | ||
| 53 | ;;; Bugs & Warnings: | ||
| 54 | ;;; | ||
| 55 | ;;; - THIS CODE IS FOR USE WITH FSF EMACS 19.21 or later. | ||
| 56 | ;;; It can cause earlier versions of emacs to crash, due to a bug in the | ||
| 57 | ;;; mouse code. | ||
| 58 | ;;; | ||
| 59 | ;;; - Using this code does slow emacs down. "banish" mode shouldn't | ||
| 60 | ;;; ever be too bad though, and on my workstation even "animate" doesn't | ||
| 61 | ;;; seem to have a noticable effect. | ||
| 62 | ;;; | ||
| 63 | ;;; - There are some situations where it doesn't do what you expect, | ||
| 64 | ;;; notably when there are long wrapped lines in the buffer. Since | ||
| 65 | ;;; there is no low-level command for finding point's position | ||
| 66 | ;;; on the screen, it can fail to move the pointer when on such a line. | ||
| 67 | ;;; | ||
| 68 | ;;; Copyright (c) 1993 Free Software Foundation | ||
| 69 | ;;; This program is free software; you can redistribute it and/or modify | ||
| 70 | ;;; it under the terms of the GNU General Public License as published by | ||
| 71 | ;;; the Free Software Foundation; either version 2, or (at your option) | ||
| 72 | ;;; any later version. | ||
| 73 | ;;; | ||
| 74 | ;;; This program is distributed in the hope that it will be useful, | ||
| 75 | ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 76 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 77 | ;;; GNU General Public License for more details. | ||
| 78 | ;;; | ||
| 79 | ;;; A copy of the GNU General Public License can be obtained from this | ||
| 80 | ;;; program's author or from the Free Software Foundation, Inc., 675 | ||
| 81 | ;;; Mass Ave, Cambridge, MA 02139, USA. | ||
| 82 | |||
| 83 | ;;; LCD Archive Entry: | ||
| 84 | ;;; avoid|Boris Goldowsky|boris@cs.rochester.edu| | ||
| 85 | ;;; Keep mouse pointer away from cursor| | ||
| 86 | ;;; $Date: 93/11/22 15:34:50 $ |$Revision: 1.10 $|| | ||
| 87 | |||
| 88 | ;;; Credits: | ||
| 89 | ;;; This code was helped by all those who contributed suggestions, fixes, and | ||
| 90 | ;;; additions: | ||
| 91 | ;;; Joe Harrington (and his advisor), for the original inspiration | ||
| 92 | ;;; Ken Manheimer, for dreaming up the Protean mode | ||
| 93 | ;;; Richard Stallman, for the awful cat-and-mouse pun, among other things | ||
| 94 | ;;; Mike Williams, Denis Howe, Bill Benedetto, Chris Moore, Don Morris, | ||
| 95 | ;;; Simon Marshall, and M.S. Ashton, for their feedback. | ||
| 96 | ;;; | ||
| 97 | ;;; Code: | ||
| 98 | |||
| 99 | (provide 'avoid) | ||
| 100 | |||
| 101 | (defvar mouse-avoidance-mode nil | ||
| 102 | "Value is t or a symbol if the mouse pointer should avoid the cursor. | ||
| 103 | See function mouse-avoidance-mode for possible values. Changing this | ||
| 104 | variable is NOT the recommended way to change modes; use the function | ||
| 105 | instead.") | ||
| 106 | |||
| 107 | (defvar mouse-avoidance-nudge-dist 4 | ||
| 108 | "*Average distance that mouse will be moved when approached by cursor. | ||
| 109 | Only applies in mode-avoidance-modes `animate' and `jump'.") | ||
| 110 | |||
| 111 | (defvar mouse-avoidance-nudge-var 3 | ||
| 112 | "*Variability of mouse-avoidance-nudge-dist (which see).") | ||
| 113 | |||
| 114 | (defvar mouse-avoidance-animation-delay .01 | ||
| 115 | "Delay between animation steps, in seconds.") | ||
| 116 | |||
| 117 | (defvar mouse-avoidance-threshhold 5 | ||
| 118 | "*Mouse-pointer's flight distance. | ||
| 119 | If the cursor gets closer than this, the mouse pointer will move away. | ||
| 120 | Only applies in mouse-avoidance-modes `animate' and `jump'.") | ||
| 121 | |||
| 122 | ;; Internal variables for mouse-avoidance-random-shape | ||
| 123 | (defvar mouse-avoidance-pointer-shapes nil) | ||
| 124 | (defvar mouse-avoidance-n-pointer-shapes 0) | ||
| 125 | |||
| 126 | ;;; Functions: | ||
| 127 | |||
| 128 | (defun mouse-avoidance-too-close-p () | ||
| 129 | ;; Return t if mouse pointer and point cursor are too close. | ||
| 130 | ;; Acceptable distance is defined by mouse-avoidance-threshhold. | ||
| 131 | (let ((mouse (mouse-position))) | ||
| 132 | (and (car (cdr mouse)) | ||
| 133 | (< (abs (- (car (cdr mouse)) (current-column))) | ||
| 134 | mouse-avoidance-threshhold) | ||
| 135 | (< (abs (- (cdr (cdr mouse)) | ||
| 136 | (+ (car (cdr (window-edges))) | ||
| 137 | (count-lines (window-start) (point))))) | ||
| 138 | mouse-avoidance-threshhold)))) | ||
| 139 | |||
| 140 | (defun mouse-avoidance-banish-mouse () | ||
| 141 | ;; Put the mouse pointer in the upper-right corner of the current frame. | ||
| 142 | (set-mouse-position (selected-frame) (1- (frame-width)) 0)) | ||
| 143 | |||
| 144 | (defun mouse-avoidance-nudge-mouse () | ||
| 145 | ;; Push the mouse a little way away, possibly animating the move | ||
| 146 | (let* ((cur (mouse-position)) | ||
| 147 | (deltax (* (+ mouse-avoidance-nudge-dist | ||
| 148 | (random mouse-avoidance-nudge-var)) | ||
| 149 | (if (zerop (random 2)) 1 -1))) | ||
| 150 | (deltay (* (+ mouse-avoidance-nudge-dist | ||
| 151 | (random mouse-avoidance-nudge-var)) | ||
| 152 | (if (zerop (random 2)) 1 -1)))) | ||
| 153 | (if (or (eq mouse-avoidance-mode 'animate) | ||
| 154 | (eq mouse-avoidance-mode 'proteus)) | ||
| 155 | (let ((i 0.0) | ||
| 156 | (color (cdr (assoc 'mouse-color (frame-parameters))))) | ||
| 157 | (while (<= i 1) | ||
| 158 | (set-mouse-position | ||
| 159 | (car cur) | ||
| 160 | (mod (+ (car (cdr cur)) (round (* i deltax))) (window-width)) | ||
| 161 | (mod (+ (cdr (cdr cur)) (round (* i deltay))) (window-height))) | ||
| 162 | (setq i (+ i (/ 1.0 mouse-avoidance-nudge-dist))) | ||
| 163 | (if (eq mouse-avoidance-mode 'proteus) | ||
| 164 | (progn | ||
| 165 | (setq x-pointer-shape (mouse-avoidance-random-shape)) | ||
| 166 | (set-mouse-color color))) | ||
| 167 | (sit-for mouse-avoidance-animation-delay))) | ||
| 168 | (set-mouse-position | ||
| 169 | (car cur) | ||
| 170 | (mod (+ (car (cdr cur)) deltax) (window-width)) | ||
| 171 | (mod (+ (cdr (cdr cur)) deltay) (window-height)))))) | ||
| 172 | |||
| 173 | (defun mouse-avoidance-random-shape () | ||
| 174 | "Return a random cursor shape. | ||
| 175 | This assumes that any variable whose name begins with x-pointer- and | ||
| 176 | has an integer value is a valid cursor shape. You might want to | ||
| 177 | redefine this function to suit your own tastes." | ||
| 178 | (if (null mouse-avoidance-pointer-shapes) | ||
| 179 | (progn | ||
| 180 | (setq mouse-avoidance-pointer-shapes | ||
| 181 | (mapcar '(lambda (x) (symbol-value (intern x))) | ||
| 182 | (all-completions "x-pointer-" obarray | ||
| 183 | '(lambda (x) | ||
| 184 | (and (boundp x) | ||
| 185 | (integerp (symbol-value x))))))) | ||
| 186 | (setq mouse-avoidance-n-pointer-shapes | ||
| 187 | (length mouse-avoidance-pointer-shapes)))) | ||
| 188 | (nth (random mouse-avoidance-n-pointer-shapes) | ||
| 189 | mouse-avoidance-pointer-shapes)) | ||
| 190 | |||
| 191 | (defun mouse-avoidance-simple-hook () | ||
| 192 | (if (and (mouse-avoidance-keyboard-command (this-command-keys))) | ||
| 193 | (mouse-avoidance-banish-mouse))) | ||
| 194 | |||
| 195 | (defun mouse-avoidance-fancy-hook () | ||
| 196 | (if (and (mouse-avoidance-keyboard-command (this-command-keys)) | ||
| 197 | (mouse-avoidance-too-close-p)) | ||
| 198 | (mouse-avoidance-nudge-mouse))) | ||
| 199 | |||
| 200 | (defun mouse-avoidance-keyboard-command (key) | ||
| 201 | "Return t if the KEYSEQENCE is composed of keyboard events only. | ||
| 202 | Returns nil if there are any lists in the key sequence." | ||
| 203 | (cond ((null key) nil) ; Null event seems to be | ||
| 204 | ; returned occasionally. | ||
| 205 | ((not (vectorp key)) t) ; Strings are keyboard events. | ||
| 206 | ((catch 'done | ||
| 207 | (let ((i 0) | ||
| 208 | (l (length key))) | ||
| 209 | (while (< i l) | ||
| 210 | (if (listp (aref key i)) | ||
| 211 | (throw 'done nil)) | ||
| 212 | (setq i (1+ i)))) | ||
| 213 | t)))) | ||
| 214 | |||
| 215 | (defun mouse-avoidance-mode (&optional mode) | ||
| 216 | "Set cursor avoidance mode to MODE. | ||
| 217 | MODE should be one of the symbols `banish', `jump', `animate', | ||
| 218 | `cat-and-mouse', or `none'. `Animate' is the same as `cat-and-mouse'. | ||
| 219 | If MODE is nil, toggle mouse avoidance. Positive numbers and | ||
| 220 | symbols other than the above are treated as equivalent to `banish'; | ||
| 221 | negative numbers and `-' are equivalent to `none'." | ||
| 222 | (interactive | ||
| 223 | (list (intern (completing-read | ||
| 224 | "Select cursor avoidance technique (SPACE for list): " | ||
| 225 | '(("banish") ("jump") ("animate") ("cat-and-mouse") | ||
| 226 | ("proteus") ("none")) | ||
| 227 | nil t)))) | ||
| 228 | (if (eq mode 'cat-and-mouse) | ||
| 229 | (setq mode 'animate)) | ||
| 230 | (setq post-command-hook | ||
| 231 | (delete 'mouse-avoidance-simple-hook (append post-command-hook nil))) | ||
| 232 | (setq post-command-hook | ||
| 233 | (delete 'mouse-avoidance-fancy-hook (append post-command-hook nil))) | ||
| 234 | (cond ((eq mode 'none) | ||
| 235 | (setq mouse-avoidance-mode nil)) | ||
| 236 | ((or (eq mode 'jump) | ||
| 237 | (eq mode 'animate) | ||
| 238 | (eq mode 'proteus)) | ||
| 239 | (add-hook 'post-command-hook 'mouse-avoidance-fancy-hook) | ||
| 240 | (setq mouse-avoidance-mode mode)) | ||
| 241 | ((or (eq mode 'banish) | ||
| 242 | (eq mode t) | ||
| 243 | (and (null mode) (null mouse-avoidance-mode)) | ||
| 244 | (and mode (> (prefix-numeric-value mode) 0))) | ||
| 245 | (add-hook 'post-command-hook 'mouse-avoidance-simple-hook) | ||
| 246 | (setq mouse-avoidance-mode 'banish)) | ||
| 247 | (t (setq mouse-avoidance-mode nil)))) | ||
| 248 | |||
| 249 | (or (assq 'mouse-avoidance-mode minor-mode-alist) | ||
| 250 | (setq minor-mode-alist (cons '(mouse-avoidance-mode " Avoid") | ||
| 251 | minor-mode-alist))) | ||
| 252 | |||
| 253 | ;;; End of avoid.el | ||
| 254 | |||
diff --git a/lisp/shadowfile.el b/lisp/shadowfile.el new file mode 100644 index 00000000000..197e51b3796 --- /dev/null +++ b/lisp/shadowfile.el | |||
| @@ -0,0 +1,830 @@ | |||
| 1 | ;;; shadowfile.el -- automatic file copying for Emacs 19 | ||
| 2 | |||
| 3 | ;;; Author: Boris Goldowsky <boris@cs.rochester.edu> | ||
| 4 | ;;; Keywords: comm | ||
| 5 | |||
| 6 | ;;; Copyright (c) 1993 Free Software Foundation | ||
| 7 | ;;; | ||
| 8 | ;;; This program is free software; you can redistribute it and/or modify | ||
| 9 | ;;; it under the terms of the GNU General Public License as published by | ||
| 10 | ;;; the Free Software Foundation; either version 2, or (at your option) | ||
| 11 | ;;; any later version. | ||
| 12 | ;;; | ||
| 13 | ;;; This program is distributed in the hope that it will be useful, | ||
| 14 | ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 15 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 16 | ;;; GNU General Public License for more details. | ||
| 17 | ;;; | ||
| 18 | ;;; A copy of the GNU General Public License can be obtained from this | ||
| 19 | ;;; program's author or from the Free Software Foundation, Inc., 675 | ||
| 20 | ;;; Mass Ave, Cambridge, MA 02139, USA. | ||
| 21 | |||
| 22 | ;;; LCD Archive Entry: | ||
| 23 | ;;; shadowfile|Boris Goldowsky|boris@cs.rochester.edu| | ||
| 24 | ;;; Helps you keep identical copies of files in multiple places.| | ||
| 25 | ;;; $Date: 93/11/17 08:46:07 $ |$Revision: 2.8 $|~/misc/shadowfile.el.Z| | ||
| 26 | |||
| 27 | ;;; Commentary: | ||
| 28 | ;;; | ||
| 29 | ;;; This package helps you to keep identical copies of files in more than one | ||
| 30 | ;;; place - possibly on different machines. When you save a file, it checks | ||
| 31 | ;;; whether it is on the list of files with "shadows", and if so, it tries to | ||
| 32 | ;;; copy it when you exit emacs (or use the shadow-copy-files command). | ||
| 33 | |||
| 34 | ;;; Installation & Use: | ||
| 35 | ;;; | ||
| 36 | ;;; Put (require 'shadowfile) in your .emacs; add clusters (if necessary) | ||
| 37 | ;;; and file groups with shadow-define-cluster, shadow-define-literal-group, | ||
| 38 | ;;; and shadow-define-regexp-group (see the documentation for these functions | ||
| 39 | ;;; for information on how and when to use them). After doing this once, | ||
| 40 | ;;; everything should be automatic. | ||
| 41 | ;;; The lists of clusters and shadows are saved in a file called | ||
| 42 | ;;; .shadows, so that they can be remembered from one emacs session to | ||
| 43 | ;;; another, even (as much as possible) if the emacs session terminates | ||
| 44 | ;;; abnormally. The files needing to be copied are stored in .shadow_todo; if | ||
| 45 | ;;; a file cannot be copied for any reason, it will stay on the list to be | ||
| 46 | ;;; tried again next time. The .shadows file should itself have shadows on | ||
| 47 | ;;; all your accounts so that the information in it is consistent everywhere, | ||
| 48 | ;;; but .shadow_todo is local information and should have no shadows. | ||
| 49 | ;;; If you do not want to copy a particular file, you can answer "no" | ||
| 50 | ;;; and be asked again next time you hit C-x 4 s or exit emacs. If you do not | ||
| 51 | ;;; want to be asked again, use shadow-cancel, and you will not be asked until | ||
| 52 | ;;; you change the file and save it again. If you do not want to shadow | ||
| 53 | ;;; that file ever again, you can edit it out of the .shadows buffer. | ||
| 54 | ;;; Anytime you edit the .shadows buffer, you must type M-x shadow-read-files | ||
| 55 | ;;; to load in the new information, or your changes will be overwritten! | ||
| 56 | |||
| 57 | ;;; Bugs & Warnings: | ||
| 58 | ;;; | ||
| 59 | ;;; - It is bad to have two emacses both running shadowfile at the same | ||
| 60 | ;;; time. It tries to detect this condition, but is not always successful. | ||
| 61 | ;;; | ||
| 62 | ;;; - You have to be careful not to edit a file in two locations | ||
| 63 | ;;; before shadowfile has had a chance to copy it; otherwise | ||
| 64 | ;;; "updating shadows" will overwrite one of the changed versions. | ||
| 65 | ;;; | ||
| 66 | ;;; - It ought to check modification times of both files to make sure | ||
| 67 | ;;; it is doing the right thing. This will have to wait until | ||
| 68 | ;;; file-newer-than-file-p works between machines. | ||
| 69 | ;;; | ||
| 70 | ;;; - It will not make directories for you, it just fails to copy files | ||
| 71 | ;;; that belong in non-existent directories. | ||
| 72 | ;;; | ||
| 73 | ;;; Please report any bugs to me (boris@cs.rochester.edu). Also let me know | ||
| 74 | ;;; if you have suggestions or would like to be informed of updates. | ||
| 75 | |||
| 76 | ;;; Code: | ||
| 77 | |||
| 78 | (provide 'shadowfile) | ||
| 79 | (require 'ange-ftp) | ||
| 80 | |||
| 81 | (setq find-file-visit-truename t) ; makes life easier with symbolic links | ||
| 82 | |||
| 83 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 84 | ;;; Variables | ||
| 85 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 86 | |||
| 87 | (defvar shadow-noquery nil | ||
| 88 | "*If nonnil, always copy shadow files without asking.") | ||
| 89 | |||
| 90 | (defvar shadow-inhibit-message nil | ||
| 91 | "*If nonnil, do not display a message when a file needs copying.") | ||
| 92 | |||
| 93 | (defvar shadow-inhibit-overload nil | ||
| 94 | "If nonnil, shadowfile won't redefine C-x C-c. | ||
| 95 | Normally it overloads the function `save-buffers-kill-emacs' to check | ||
| 96 | for files have been changed and need to be copied to other systems.") | ||
| 97 | |||
| 98 | (defvar shadow-info-file nil | ||
| 99 | "File to keep shadow information in. | ||
| 100 | The shadow-info-file should be shadowed to all your accounts to | ||
| 101 | ensure consistency. Default: ~/.shadows") | ||
| 102 | |||
| 103 | (defvar shadow-todo-file nil | ||
| 104 | "File to store the list of uncopied shadows in. | ||
| 105 | This means that if a remote system is down, or for any reason you cannot or | ||
| 106 | decide not to copy your shadow files at the end of one emacs session, it will | ||
| 107 | remember and ask you again in your next emacs session. | ||
| 108 | This file must NOT be shadowed to any other system, it is host-specific. | ||
| 109 | Default: ~/.shadow_todo") | ||
| 110 | |||
| 111 | ;;; The following two variables should in most cases initialize themselves | ||
| 112 | ;;; correctly. They are provided as variables in case the defaults are wrong | ||
| 113 | ;;; on your machine \(and for efficiency). | ||
| 114 | |||
| 115 | (defvar shadow-system-name (system-name) | ||
| 116 | "The complete hostname of this machine.") | ||
| 117 | |||
| 118 | (defvar shadow-homedir nil | ||
| 119 | "Your home directory on this machine.") | ||
| 120 | |||
| 121 | ;;; | ||
| 122 | ;;; Internal variables whose values are stored in the info and todo files: | ||
| 123 | ;;; | ||
| 124 | |||
| 125 | (defvar shadow-clusters nil | ||
| 126 | "List of host clusters \(see shadow-define-cluster).") | ||
| 127 | |||
| 128 | (defvar shadow-literal-groups nil | ||
| 129 | "List of files that are shared between hosts. | ||
| 130 | This list contains shadow structures with literal filenames, created by | ||
| 131 | shadow-define-group.") | ||
| 132 | |||
| 133 | (defvar shadow-regexp-groups nil | ||
| 134 | "List of file types that are shared between hosts. | ||
| 135 | This list contains shadow structures with regexps matching filenames, | ||
| 136 | created by shadow-define-regexp-group.") | ||
| 137 | |||
| 138 | ;;; | ||
| 139 | ;;; Other internal variables: | ||
| 140 | ;;; | ||
| 141 | |||
| 142 | (defvar shadow-files-to-copy nil) ; List of files that need to | ||
| 143 | ; be copied to remote hosts. | ||
| 144 | |||
| 145 | (defvar shadow-hashtable nil) ; for speed | ||
| 146 | |||
| 147 | (defvar shadow-info-buffer nil) ; buf visiting shadow-info-file | ||
| 148 | (defvar shadow-todo-buffer nil) ; buf visiting shadow-todo-file | ||
| 149 | |||
| 150 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 151 | ;;; Syntactic sugar; General list and string manipulation | ||
| 152 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 153 | |||
| 154 | (defmacro shadow-when (condition &rest body) | ||
| 155 | ;; From cl.el | ||
| 156 | "(shadow-when CONDITION . BODY) => evaluate BODY if CONDITION is true." | ||
| 157 | (` (if (not (, condition)) () (,@ body)))) | ||
| 158 | |||
| 159 | (defun shadow-union (a b) | ||
| 160 | "Add members of list A to list B | ||
| 161 | if they are not equal to items already in B." | ||
| 162 | (if (null a) | ||
| 163 | b | ||
| 164 | (if (member (car a) b) | ||
| 165 | (shadow-union (cdr a) b) | ||
| 166 | (shadow-union (cdr a) (cons (car a) b))))) | ||
| 167 | |||
| 168 | (defun shadow-find (func list) | ||
| 169 | "If FUNC applied to some element of LIST is nonnil, | ||
| 170 | return the first such element." | ||
| 171 | (while (and list (not (funcall func (car list)))) | ||
| 172 | (setq list (cdr list))) | ||
| 173 | (car list)) | ||
| 174 | |||
| 175 | (defun shadow-remove-if (func list) | ||
| 176 | "Remove elements satisfying FUNC from LIST. | ||
| 177 | Nondestructive; actually returns a copy of the list with the elements removed." | ||
| 178 | (if list | ||
| 179 | (if (funcall func (car list)) | ||
| 180 | (shadow-remove-if func (cdr list)) | ||
| 181 | (cons (car list) (shadow-remove-if func (cdr list)))) | ||
| 182 | nil)) | ||
| 183 | |||
| 184 | (defun shadow-join (strings sep) | ||
| 185 | "Concatenate elements of the list of STRINGS with SEP between each." | ||
| 186 | (cond ((null strings) "") | ||
| 187 | ((null (cdr strings)) (car strings)) | ||
| 188 | ((concat (car strings) " " (shadow-join (cdr strings) sep))))) | ||
| 189 | |||
| 190 | (defun shadow-regexp-superquote (string) | ||
| 191 | "Like regexp-quote, but includes the ^ and $ | ||
| 192 | to make sure regexp matches nothing but STRING." | ||
| 193 | (concat "^" (regexp-quote string) "$")) | ||
| 194 | |||
| 195 | (defun shadow-suffix (prefix string) | ||
| 196 | "If PREFIX begins STRING, return the rest. | ||
| 197 | Return value is nonnil if PREFIX and STRING are string= up to the length of | ||
| 198 | PREFIX." | ||
| 199 | (let ((lp (length prefix)) | ||
| 200 | (ls (length string))) | ||
| 201 | (if (and (>= ls lp) | ||
| 202 | (string= prefix (substring string 0 lp))) | ||
| 203 | (substring string lp)))) | ||
| 204 | |||
| 205 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 206 | ;;; Clusters and sites | ||
| 207 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 208 | |||
| 209 | ;;; I use the term `site' to refer to a string which may be the name of a | ||
| 210 | ;;; cluster or a literal hostname. All user-level commands should accept | ||
| 211 | ;;; either. | ||
| 212 | |||
| 213 | (defun shadow-make-cluster (name primary regexp) | ||
| 214 | "Creates a shadow cluster | ||
| 215 | called NAME, using the PRIMARY hostname, REGEXP matching all hosts in the | ||
| 216 | cluster. The variable shadow-clusters associates the names of clusters to | ||
| 217 | these structures. | ||
| 218 | This function is for program use: to create clusters interactively, use | ||
| 219 | shadow-define-cluster instead." | ||
| 220 | (list name primary regexp)) | ||
| 221 | |||
| 222 | (defmacro shadow-cluster-name (cluster) | ||
| 223 | "Return the name of the CLUSTER." | ||
| 224 | (list 'elt cluster 0)) | ||
| 225 | |||
| 226 | (defmacro shadow-cluster-primary (cluster) | ||
| 227 | "Return the primary hostname of a CLUSTER." | ||
| 228 | (list 'elt cluster 1)) | ||
| 229 | |||
| 230 | (defmacro shadow-cluster-regexp (cluster) | ||
| 231 | "Return the regexp matching hosts in a CLUSTER." | ||
| 232 | (list 'elt cluster 2)) | ||
| 233 | |||
| 234 | (defun shadow-set-cluster (name primary regexp) | ||
| 235 | "Put cluster NAME on the list of clusters, | ||
| 236 | replacing old definition if any. PRIMARY and REGEXP are the | ||
| 237 | information defining the cluster. For interactive use, call | ||
| 238 | shadow-define-cluster instead." | ||
| 239 | (let ((rest (shadow-remove-if | ||
| 240 | (function (lambda (x) (equal name (car x)))) | ||
| 241 | shadow-clusters))) | ||
| 242 | (setq shadow-clusters | ||
| 243 | (cons (shadow-make-cluster name primary regexp) | ||
| 244 | rest)))) | ||
| 245 | |||
| 246 | (defmacro shadow-get-cluster (name) | ||
| 247 | "Return cluster named NAME, or nil." | ||
| 248 | (list 'assoc name 'shadow-clusters)) | ||
| 249 | |||
| 250 | (defun shadow-site-primary (site) | ||
| 251 | "If SITE is a cluster, return primary host, otherwise return SITE." | ||
| 252 | (let ((c (shadow-get-cluster site))) | ||
| 253 | (if c | ||
| 254 | (shadow-cluster-primary c) | ||
| 255 | site))) | ||
| 256 | |||
| 257 | ;;; SITES | ||
| 258 | |||
| 259 | (defun shadow-site-cluster (site) | ||
| 260 | "Given a SITE \(hostname or cluster name), return the cluster | ||
| 261 | that it is in, or nil." | ||
| 262 | (or (assoc site shadow-clusters) | ||
| 263 | (shadow-find | ||
| 264 | (function (lambda (x) | ||
| 265 | (string-match (shadow-cluster-regexp x) | ||
| 266 | site))) | ||
| 267 | shadow-clusters))) | ||
| 268 | |||
| 269 | (defun shadow-read-site () | ||
| 270 | "Read a cluster name or hostname from the minibuffer." | ||
| 271 | (let ((ans (completing-read "Host or cluster name [RET when done]: " | ||
| 272 | shadow-clusters))) | ||
| 273 | (if (equal "" ans) | ||
| 274 | nil | ||
| 275 | ans))) | ||
| 276 | |||
| 277 | (defun shadow-site-match (site1 site2) | ||
| 278 | "Nonnil iff SITE1 is or includes SITE2. | ||
| 279 | Each may be a host or cluster name; if they are clusters, regexp of site1 will | ||
| 280 | be matched against the primary of site2." | ||
| 281 | (or (string-equal site1 site2) ; quick check | ||
| 282 | (let* ((cluster1 (shadow-get-cluster site1)) | ||
| 283 | (primary2 (shadow-site-primary site2))) | ||
| 284 | (if cluster1 | ||
| 285 | (string-match (shadow-cluster-regexp cluster1) primary2) | ||
| 286 | (string-equal site1 primary2))))) | ||
| 287 | |||
| 288 | (defun shadow-get-user (site) | ||
| 289 | "Returns the default username for a site." | ||
| 290 | (ange-ftp-get-user (shadow-site-primary site))) | ||
| 291 | |||
| 292 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 293 | ;;; Filename manipulation | ||
| 294 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 295 | |||
| 296 | (defun shadow-parse-fullpath (fullpath) | ||
| 297 | "Parse PATH into \(site user path) list, | ||
| 298 | or leave it alone if it already is one. Returns nil if the argument is not a | ||
| 299 | full ange-ftp pathname." | ||
| 300 | (if (listp fullpath) | ||
| 301 | fullpath | ||
| 302 | (ange-ftp-ftp-name fullpath))) | ||
| 303 | |||
| 304 | (defun shadow-parse-path (path) | ||
| 305 | "Parse any PATH into \(site user path) list. | ||
| 306 | Argument can be a simple path, full ange-ftp path, or already a hup list." | ||
| 307 | (or (shadow-parse-fullpath path) | ||
| 308 | (list shadow-system-name | ||
| 309 | (user-login-name) | ||
| 310 | path))) | ||
| 311 | |||
| 312 | (defsubst shadow-make-fullpath (host user path) | ||
| 313 | "Make an ange-ftp style fullpath out of HOST, USER (optional), and PATH. | ||
| 314 | This is probably not as general as it ought to be." | ||
| 315 | (concat "/" | ||
| 316 | (if user (concat user "@")) | ||
| 317 | host ":" | ||
| 318 | path)) | ||
| 319 | |||
| 320 | (defun shadow-replace-path-component (fullpath newpath) | ||
| 321 | "Return FULLPATH with the pathname component changed to NEWPATH." | ||
| 322 | (let ((hup (shadow-parse-fullpath fullpath))) | ||
| 323 | (shadow-make-fullpath (nth 0 hup) (nth 1 hup) newpath))) | ||
| 324 | |||
| 325 | (defun shadow-local-file (file) | ||
| 326 | "If FILENAME is at this site, | ||
| 327 | remove /user@host part. If refers to a different system or a different user on | ||
| 328 | this system, return nil." | ||
| 329 | (let ((hup (shadow-parse-fullpath file))) | ||
| 330 | (cond ((null hup) file) | ||
| 331 | ((and (shadow-site-match (nth 0 hup) shadow-system-name) | ||
| 332 | (string-equal (nth 1 hup) (user-login-name))) | ||
| 333 | (nth 2 hup)) | ||
| 334 | (t nil)))) | ||
| 335 | |||
| 336 | (defun shadow-expand-cluster-in-file-name (file) | ||
| 337 | "If hostname part of FILE is a cluster, expand it | ||
| 338 | into the cluster's primary hostname. Will return the pathname bare if it is | ||
| 339 | a local file." | ||
| 340 | (let ((hup (shadow-parse-path file)) | ||
| 341 | cluster) | ||
| 342 | (cond ((null hup) file) | ||
| 343 | ((shadow-local-file hup)) | ||
| 344 | ((shadow-make-fullpath (shadow-site-primary (nth 0 hup)) | ||
| 345 | (nth 1 hup) | ||
| 346 | (nth 2 hup)))))) | ||
| 347 | |||
| 348 | (defun shadow-expand-file-name (file &optional default) | ||
| 349 | "Expand file name and get file's true name." | ||
| 350 | (file-truename (expand-file-name file default))) | ||
| 351 | |||
| 352 | (defun shadow-contract-file-name (file) | ||
| 353 | "Simplify FILENAME | ||
| 354 | by replacing (when possible) home directory with ~, and hostname with cluster | ||
| 355 | name that includes it. Filename should be absolute and true." | ||
| 356 | (let* ((hup (shadow-parse-path file)) | ||
| 357 | (homedir (if (shadow-local-file hup) | ||
| 358 | shadow-homedir | ||
| 359 | (file-name-as-directory | ||
| 360 | (nth 2 (shadow-parse-fullpath | ||
| 361 | (expand-file-name | ||
| 362 | (shadow-make-fullpath | ||
| 363 | (nth 0 hup) (nth 1 hup) "~"))))))) | ||
| 364 | (suffix (shadow-suffix homedir (nth 2 hup))) | ||
| 365 | (cluster (shadow-site-cluster (nth 0 hup)))) | ||
| 366 | (shadow-make-fullpath | ||
| 367 | (if cluster | ||
| 368 | (shadow-cluster-name cluster) | ||
| 369 | (nth 0 hup)) | ||
| 370 | (nth 1 hup) | ||
| 371 | (if suffix | ||
| 372 | (concat "~/" suffix) | ||
| 373 | (nth 2 hup))))) | ||
| 374 | |||
| 375 | (defun shadow-same-site (pattern file) | ||
| 376 | "True if the site of PATTERN and of FILE are on the same site. | ||
| 377 | If usernames are supplied, they must also match exactly. PATTERN and FILE may | ||
| 378 | be lists of host, user, path, or ange-ftp pathnames. FILE may also be just a | ||
| 379 | local filename." | ||
| 380 | (let ((pattern-sup (shadow-parse-fullpath pattern)) | ||
| 381 | (file-sup (shadow-parse-path file))) | ||
| 382 | (and | ||
| 383 | (shadow-site-match (nth 0 pattern-sup) (nth 0 file-sup)) | ||
| 384 | (or (null (nth 1 pattern-sup)) | ||
| 385 | (string-equal (nth 1 pattern-sup) (nth 1 file-sup)))))) | ||
| 386 | |||
| 387 | (defun shadow-file-match (pattern file &optional regexp) | ||
| 388 | "Returns t if PATTERN matches FILE. | ||
| 389 | If REGEXP is supplied and nonnil, the pathname part of the pattern is a regular | ||
| 390 | expression, otherwise it must match exactly. The sites and usernames must | ||
| 391 | match---see shadow-same-site. The pattern must be in full ange-ftp format, but | ||
| 392 | the file can be any valid filename. This function does not do any filename | ||
| 393 | expansion or contraction, you must do that yourself first." | ||
| 394 | (let* ((pattern-sup (shadow-parse-fullpath pattern)) | ||
| 395 | (file-sup (shadow-parse-path file))) | ||
| 396 | (and (shadow-same-site pattern-sup file-sup) | ||
| 397 | (if regexp | ||
| 398 | (string-match (nth 2 pattern-sup) (nth 2 file-sup)) | ||
| 399 | (string-equal (nth 2 pattern-sup) (nth 2 file-sup)))))) | ||
| 400 | |||
| 401 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 402 | ;;; User-level Commands | ||
| 403 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 404 | |||
| 405 | (defun shadow-define-cluster (name) | ||
| 406 | "Edit \(or create) the definition of a cluster. | ||
| 407 | This is a group of hosts that share directories, so that copying to or from | ||
| 408 | one of them is sufficient to update the file on all of them. Clusters are | ||
| 409 | defined by a name, the network address of a primary host \(the one we copy | ||
| 410 | files to), and a regular expression that matches the hostnames of all the sites | ||
| 411 | in the cluster." | ||
| 412 | (interactive (list (completing-read "Cluster name: " shadow-clusters () ()))) | ||
| 413 | (let* ((old (shadow-get-cluster name)) | ||
| 414 | (primary (read-string "Primary host: " | ||
| 415 | (if old (shadow-cluster-primary old) | ||
| 416 | name))) | ||
| 417 | (regexp (let (try-regexp) | ||
| 418 | (while (not | ||
| 419 | (string-match | ||
| 420 | (setq try-regexp | ||
| 421 | (read-string | ||
| 422 | "Regexp matching all host names: " | ||
| 423 | (if old (shadow-cluster-regexp old) | ||
| 424 | (shadow-regexp-superquote primary)))) | ||
| 425 | primary)) | ||
| 426 | (message "Regexp doesn't include the primary host!") | ||
| 427 | (sit-for 2)) | ||
| 428 | try-regexp)) | ||
| 429 | ; (username (read-no-blanks-input | ||
| 430 | ; (format "Username [default: %s]: " | ||
| 431 | ; (shadow-get-user primary)) | ||
| 432 | ; (if old (or (shadow-cluster-username old) "") | ||
| 433 | ; (user-login-name)))) | ||
| 434 | ) | ||
| 435 | ; (if (string-equal "" username) (setq username nil)) | ||
| 436 | (shadow-set-cluster name primary regexp))) | ||
| 437 | |||
| 438 | (defun shadow-define-literal-group () | ||
| 439 | "Declare a single file to be shared between sites. | ||
| 440 | It may have different filenames on each site. When this file is edited, the | ||
| 441 | new version will be copied to each of the other locations. Sites can be | ||
| 442 | specific hostnames, or names of clusters \(see shadow-define-cluster)." | ||
| 443 | (interactive) | ||
| 444 | (let* ((hup (shadow-parse-fullpath | ||
| 445 | (shadow-contract-file-name (buffer-file-name)))) | ||
| 446 | (path (nth 2 hup)) | ||
| 447 | user site group) | ||
| 448 | (while (setq site (shadow-read-site)) | ||
| 449 | (setq user (read-string (format "Username [default %s]: " | ||
| 450 | (shadow-get-user site))) | ||
| 451 | path (read-string "Filename: " path)) | ||
| 452 | (setq group (cons (shadow-make-fullpath site | ||
| 453 | (if (string-equal "" user) | ||
| 454 | (shadow-get-user site) | ||
| 455 | user) | ||
| 456 | path) | ||
| 457 | group))) | ||
| 458 | (setq shadow-literal-groups (cons group shadow-literal-groups))) | ||
| 459 | (shadow-write-info-file)) | ||
| 460 | |||
| 461 | (defun shadow-define-regexp-group () | ||
| 462 | "Make each of a group of files be shared between hosts. | ||
| 463 | Prompts for regular expression; files matching this are shared between a list | ||
| 464 | of sites, which are also prompted for. The filenames must be identical on all | ||
| 465 | hosts \(if they aren't, use shadow-define-group instead of this function). | ||
| 466 | Each site can be either a hostname or the name of a cluster \(see | ||
| 467 | shadow-define-cluster)." | ||
| 468 | (interactive) | ||
| 469 | (let ((regexp (read-string | ||
| 470 | "Filename regexp: " | ||
| 471 | (if (buffer-file-name) | ||
| 472 | (shadow-regexp-superquote | ||
| 473 | (nth 2 | ||
| 474 | (shadow-parse-path | ||
| 475 | (shadow-contract-file-name | ||
| 476 | (buffer-file-name)))))))) | ||
| 477 | site sites usernames) | ||
| 478 | (while (setq site (shadow-read-site)) | ||
| 479 | (setq sites (cons site sites)) | ||
| 480 | (setq usernames | ||
| 481 | (cons (read-string (format "Username for %s: " site) | ||
| 482 | (shadow-get-user site)) | ||
| 483 | usernames))) | ||
| 484 | (setq shadow-regexp-groups | ||
| 485 | (cons (shadow-make-group regexp sites usernames) | ||
| 486 | shadow-regexp-groups)) | ||
| 487 | (shadow-write-info-file))) | ||
| 488 | |||
| 489 | (defun shadow-shadows () | ||
| 490 | ;; Mostly for debugging. | ||
| 491 | "Interactive function to display shadows of a buffer." | ||
| 492 | (interactive) | ||
| 493 | (let ((msg (shadow-join (mapcar (function cdr) | ||
| 494 | (shadow-shadows-of (buffer-file-name))) | ||
| 495 | " "))) | ||
| 496 | (message (if (zerop (length msg)) | ||
| 497 | "No shadows." | ||
| 498 | msg)))) | ||
| 499 | |||
| 500 | (defun shadow-copy-files (&optional arg) | ||
| 501 | "Copy all pending shadow files. | ||
| 502 | With prefix argument, copy all pending files without query. | ||
| 503 | Pending copies are stored in variable shadow-files-to-copy, and in | ||
| 504 | shadow-todo-file if necessary. This function is invoked by | ||
| 505 | shadow-save-buffers-kill-emacs, so it is not usually necessary to | ||
| 506 | call it manually." | ||
| 507 | (interactive "P") | ||
| 508 | (if (and (not shadow-files-to-copy) (interactive-p)) | ||
| 509 | (message "No files need to be shadowed.") | ||
| 510 | (save-excursion | ||
| 511 | (map-y-or-n-p (function | ||
| 512 | (lambda (pair) | ||
| 513 | (or arg | ||
| 514 | (format "Copy shadow file %s? " (cdr pair))))) | ||
| 515 | (function shadow-copy-file) | ||
| 516 | shadow-files-to-copy | ||
| 517 | '("shadow" "shadows" "copy")) | ||
| 518 | (shadow-write-todo-file t)))) | ||
| 519 | |||
| 520 | (defun shadow-cancel () | ||
| 521 | "Cancel the instruction to copy some files. | ||
| 522 | Prompts for which copy operations to cancel. You will not be asked to copy | ||
| 523 | them again, unless you make more changes to the files. To cancel a shadow | ||
| 524 | permanently, remove the group from shadow-literal-groups or | ||
| 525 | shadow-regexp-groups." | ||
| 526 | (interactive) | ||
| 527 | (map-y-or-n-p (function (lambda (pair) | ||
| 528 | (format "Cancel copying %s to %s? " | ||
| 529 | (car pair) (cdr pair)))) | ||
| 530 | (function (lambda (pair) | ||
| 531 | (shadow-remove-from-todo pair))) | ||
| 532 | shadow-files-to-copy | ||
| 533 | '("shadow" "shadows" "cancel copy")) | ||
| 534 | (message (format "There are %d shadows to be updated." | ||
| 535 | (length shadow-files-to-copy))) | ||
| 536 | (shadow-write-todo-file)) | ||
| 537 | |||
| 538 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 539 | ;;; Internal functions | ||
| 540 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 541 | |||
| 542 | (defun shadow-make-group (regexp sites usernames) | ||
| 543 | "Makes a description of a file group--- | ||
| 544 | actually a list of regexp ange-ftp file names---from REGEXP \(name of file to | ||
| 545 | be shadowed), list of SITES, and corresponding list of USERNAMES for each | ||
| 546 | site." | ||
| 547 | (if sites | ||
| 548 | (cons (shadow-make-fullpath (car sites) (car usernames) regexp) | ||
| 549 | (shadow-make-group regexp (cdr sites) (cdr usernames))) | ||
| 550 | nil)) | ||
| 551 | |||
| 552 | (defun shadow-copy-file (s) | ||
| 553 | "Copy one shadow file." | ||
| 554 | (let* ((buffer | ||
| 555 | (cond ((get-file-buffer (car s))) | ||
| 556 | ((not (file-readable-p (car s))) | ||
| 557 | (if (y-or-n-p | ||
| 558 | (format "Cannot find file %s--cancel copy request?" | ||
| 559 | (car s))) | ||
| 560 | (shadow-remove-from-todo s)) | ||
| 561 | nil) | ||
| 562 | ((y-or-n-p | ||
| 563 | (format "No buffer for %s -- update shadow anyway?" | ||
| 564 | (car s))) | ||
| 565 | (find-file-noselect (car s))))) | ||
| 566 | (to (shadow-expand-cluster-in-file-name (cdr s)))) | ||
| 567 | (shadow-when buffer | ||
| 568 | (set-buffer buffer) | ||
| 569 | (save-restriction | ||
| 570 | (widen) | ||
| 571 | (condition-case i | ||
| 572 | (progn | ||
| 573 | (write-region (point-min) (point-max) to) | ||
| 574 | (shadow-remove-from-todo s)) | ||
| 575 | (error (message (format "Shadow %s not updated!" | ||
| 576 | (cdr s))))))))) | ||
| 577 | |||
| 578 | (defun shadow-shadows-of (file) | ||
| 579 | "Returns copy operations needed to update FILE. | ||
| 580 | Filename should have clusters expanded, but otherwise can have any format. | ||
| 581 | Return value is a list of dotted pairs like \(from . to), where from | ||
| 582 | and to are absolute file names." | ||
| 583 | (or (symbol-value (intern-soft file shadow-hashtable)) | ||
| 584 | (let* ((absolute-file (shadow-expand-file-name | ||
| 585 | (or (shadow-local-file file) file) | ||
| 586 | shadow-homedir)) | ||
| 587 | (canonical-file (shadow-contract-file-name absolute-file)) | ||
| 588 | (shadows | ||
| 589 | (mapcar (function (lambda (shadow) | ||
| 590 | (cons absolute-file shadow))) | ||
| 591 | (append | ||
| 592 | (shadow-shadows-of-1 | ||
| 593 | canonical-file shadow-literal-groups nil) | ||
| 594 | (shadow-shadows-of-1 | ||
| 595 | canonical-file shadow-regexp-groups t))))) | ||
| 596 | (set (intern file shadow-hashtable) shadows)))) | ||
| 597 | |||
| 598 | (defun shadow-shadows-of-1 (file groups regexp) | ||
| 599 | "Return list of FILE's shadows in GROUPS, | ||
| 600 | which are considered as regular expressions if third arg REGEXP is true." | ||
| 601 | (if groups | ||
| 602 | (let ((nonmatching | ||
| 603 | (shadow-remove-if | ||
| 604 | (function (lambda (x) (shadow-file-match x file regexp))) | ||
| 605 | (car groups)))) | ||
| 606 | (append (cond ((equal nonmatching (car groups)) nil) | ||
| 607 | (regexp | ||
| 608 | (let ((realpath (nth 2 (shadow-parse-fullpath file)))) | ||
| 609 | (mapcar | ||
| 610 | (function | ||
| 611 | (lambda (x) | ||
| 612 | (shadow-replace-path-component x realpath))) | ||
| 613 | nonmatching))) | ||
| 614 | (t nonmatching)) | ||
| 615 | (shadow-shadows-of-1 file (cdr groups) regexp))))) | ||
| 616 | |||
| 617 | (defun shadow-add-to-todo () | ||
| 618 | "If current buffer has shadows, add them to the list | ||
| 619 | of files needing to be copied." | ||
| 620 | (let ((shadows (shadow-shadows-of | ||
| 621 | (shadow-expand-file-name | ||
| 622 | (buffer-file-name (current-buffer)))))) | ||
| 623 | (shadow-when shadows | ||
| 624 | (setq shadow-files-to-copy | ||
| 625 | (shadow-union shadows shadow-files-to-copy)) | ||
| 626 | (shadow-when (not shadow-inhibit-message) | ||
| 627 | (message (substitute-command-keys | ||
| 628 | "Use \\[shadow-copy-files] to update shadows.")) | ||
| 629 | (sit-for 1)) | ||
| 630 | (shadow-write-todo-file))) | ||
| 631 | nil) ; Return nil for write-file-hooks | ||
| 632 | |||
| 633 | (defun shadow-remove-from-todo (pair) | ||
| 634 | "Remove PAIR from shadow-files-to-copy. | ||
| 635 | PAIR must be (eq to) one of the elements of that list." | ||
| 636 | (setq shadow-files-to-copy | ||
| 637 | (shadow-remove-if (function (lambda (s) (eq s pair))) | ||
| 638 | shadow-files-to-copy))) | ||
| 639 | |||
| 640 | (defun shadow-read-files () | ||
| 641 | "Visits and loads shadow-info-file and shadow-todo-file, | ||
| 642 | thus restoring shadowfile's state from your last emacs session. | ||
| 643 | Returns t unless files were locked; then returns nil." | ||
| 644 | (interactive) | ||
| 645 | (if (or (stringp (file-locked-p shadow-info-file)) | ||
| 646 | (stringp (file-locked-p shadow-todo-file))) | ||
| 647 | (progn | ||
| 648 | (message "Shadowfile is running in another emacs; can't have two.") | ||
| 649 | (beep) | ||
| 650 | (sit-for 3) | ||
| 651 | nil) | ||
| 652 | (save-excursion | ||
| 653 | (shadow-when shadow-info-file | ||
| 654 | (set-buffer (setq shadow-info-buffer | ||
| 655 | (find-file-noselect shadow-info-file))) | ||
| 656 | (shadow-when (and (not (buffer-modified-p)) | ||
| 657 | (file-newer-than-file-p (make-auto-save-file-name) | ||
| 658 | shadow-info-file)) | ||
| 659 | (erase-buffer) | ||
| 660 | (message "Data recovered from %s." | ||
| 661 | (car (insert-file-contents (make-auto-save-file-name)))) | ||
| 662 | (sit-for 1)) | ||
| 663 | (eval-current-buffer)) | ||
| 664 | (shadow-when shadow-todo-file | ||
| 665 | (set-buffer (setq shadow-todo-buffer | ||
| 666 | (find-file-noselect shadow-todo-file))) | ||
| 667 | (shadow-when (and (not (buffer-modified-p)) | ||
| 668 | (file-newer-than-file-p (make-auto-save-file-name) | ||
| 669 | shadow-todo-file)) | ||
| 670 | (erase-buffer) | ||
| 671 | (message "Data recovered from %s." | ||
| 672 | (car (insert-file-contents (make-auto-save-file-name)))) | ||
| 673 | (sit-for 1)) | ||
| 674 | (eval-current-buffer nil)) | ||
| 675 | (shadow-invalidate-hashtable)) | ||
| 676 | t)) | ||
| 677 | |||
| 678 | (defun shadow-write-info-file () | ||
| 679 | "Write out information to shadow-info-file. | ||
| 680 | Also clears shadow-hashtable, since when there are new shadows defined, the old | ||
| 681 | hashtable info is invalid." | ||
| 682 | (shadow-invalidate-hashtable) | ||
| 683 | (if shadow-info-file | ||
| 684 | (save-excursion | ||
| 685 | (if (not shadow-info-buffer) | ||
| 686 | (setq shadow-info-buffer (find-file-noselect shadow-info-file))) | ||
| 687 | (set-buffer shadow-info-buffer) | ||
| 688 | (delete-region (point-min) (point-max)) | ||
| 689 | (shadow-insert-var 'shadow-clusters) | ||
| 690 | (shadow-insert-var 'shadow-literal-groups) | ||
| 691 | (shadow-insert-var 'shadow-regexp-groups)))) | ||
| 692 | |||
| 693 | (defun shadow-write-todo-file (&optional save) | ||
| 694 | "Write out information to shadow-todo-file. | ||
| 695 | With nonnil argument also saves the buffer." | ||
| 696 | (save-excursion | ||
| 697 | (if (not shadow-todo-buffer) | ||
| 698 | (setq shadow-todo-buffer (find-file-noselect shadow-todo-file))) | ||
| 699 | (set-buffer shadow-todo-buffer) | ||
| 700 | (delete-region (point-min) (point-max)) | ||
| 701 | (shadow-insert-var 'shadow-files-to-copy) | ||
| 702 | (if save (shadow-save-todo-file)))) | ||
| 703 | |||
| 704 | (defun shadow-save-todo-file () | ||
| 705 | (if (and shadow-todo-buffer (buffer-modified-p shadow-todo-buffer)) | ||
| 706 | (save-excursion | ||
| 707 | (set-buffer shadow-todo-buffer) | ||
| 708 | (condition-case nil ; have to continue even in case of | ||
| 709 | (basic-save-buffer) ; error, otherwise kill-emacs might | ||
| 710 | (error ; not work! | ||
| 711 | (message "WARNING: Can't save shadow todo file; it is locked!") | ||
| 712 | (sit-for 1)))))) | ||
| 713 | |||
| 714 | (defun shadow-invalidate-hashtable () | ||
| 715 | (setq shadow-hashtable (make-vector 37 0))) | ||
| 716 | |||
| 717 | (defun shadow-insert-var (variable) | ||
| 718 | "Prettily insert a setq command for VARIABLE. | ||
| 719 | which, when later evaluated, will restore it to its current setting. | ||
| 720 | SYMBOL must be the name of a variable whose value is a list." | ||
| 721 | (let ((standard-output (current-buffer))) | ||
| 722 | (insert (format "(setq %s" variable)) | ||
| 723 | (cond ((consp (eval variable)) | ||
| 724 | (insert "\n '(") | ||
| 725 | (prin1 (car (eval variable))) | ||
| 726 | (let ((rest (cdr (eval variable)))) | ||
| 727 | (while rest | ||
| 728 | (insert "\n ") | ||
| 729 | (prin1 (car rest)) | ||
| 730 | (setq rest (cdr rest))) | ||
| 731 | (insert "))\n\n"))) | ||
| 732 | (t (insert " ") | ||
| 733 | (prin1 (eval variable)) | ||
| 734 | (insert ")\n\n"))))) | ||
| 735 | |||
| 736 | (defun shadow-save-buffers-kill-emacs (&optional arg) | ||
| 737 | "Offer to save each buffer and copy shadows, then kill this Emacs process. | ||
| 738 | With prefix arg, silently save all file-visiting buffers, then kill. | ||
| 739 | |||
| 740 | Extended by shadowfile to automatically save `shadow-todo-file' and | ||
| 741 | look for files that have been changed and need to be copied to other systems." | ||
| 742 | ;; This function is necessary because we need to get control and save | ||
| 743 | ;; the todo file /after/ saving other files, but /before/ the warning | ||
| 744 | ;; message about unsaved buffers (because it can get modified by the | ||
| 745 | ;; action of saving other buffers). `kill-emacs-hook' is no good | ||
| 746 | ;; because it is not called at the correct time, and also because it is | ||
| 747 | ;; called when the terminal is disconnected and we cannot ask whether | ||
| 748 | ;; to copy files. | ||
| 749 | (interactive "P") | ||
| 750 | (shadow-save-todo-file) | ||
| 751 | (save-some-buffers arg t) | ||
| 752 | (shadow-copy-files) | ||
| 753 | (shadow-save-todo-file) | ||
| 754 | (and (or (not (memq t (mapcar (function | ||
| 755 | (lambda (buf) (and (buffer-file-name buf) | ||
| 756 | (buffer-modified-p buf)))) | ||
| 757 | (buffer-list)))) | ||
| 758 | (yes-or-no-p "Modified buffers exist; exit anyway? ")) | ||
| 759 | (or (not (fboundp 'process-list)) | ||
| 760 | ;; process-list is not defined on VMS. | ||
| 761 | (let ((processes (process-list)) | ||
| 762 | active) | ||
| 763 | (while processes | ||
| 764 | (and (memq (process-status (car processes)) '(run stop open)) | ||
| 765 | (let ((val (process-kill-without-query (car processes)))) | ||
| 766 | (process-kill-without-query (car processes) val) | ||
| 767 | val) | ||
| 768 | (setq active t)) | ||
| 769 | (setq processes (cdr processes))) | ||
| 770 | (or (not active) | ||
| 771 | (yes-or-no-p "Active processes exist; kill them and exit anyway? ")))) | ||
| 772 | (kill-emacs))) | ||
| 773 | |||
| 774 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 775 | ;;; Lucid Emacs compatibility (may not be complete) | ||
| 776 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 777 | |||
| 778 | (shadow-when (not (fboundp 'file-truename)) | ||
| 779 | (require 'symlink-fix) | ||
| 780 | (defun shadow-expand-file-name (file &optional default) | ||
| 781 | (symlink-expand-file-name file default))) | ||
| 782 | |||
| 783 | (shadow-when (not (fboundp 'ange-ftp-ftp-name)) | ||
| 784 | (require 'ange-ftp) | ||
| 785 | (defun shadow-parse-fullpath (fullpath) | ||
| 786 | (if (listp fullpath) | ||
| 787 | fullpath | ||
| 788 | (ange-ftp-ftp-path fullpath)))) | ||
| 789 | |||
| 790 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 791 | ;;; Hook us up | ||
| 792 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
| 793 | |||
| 794 | ;;; File shadowing is activated at load time, unless this this file is | ||
| 795 | ;;; being preloaded, in which case it is added to after-init-hook. | ||
| 796 | ;;; Thanks to Richard Caley for this scheme. | ||
| 797 | |||
| 798 | (defun shadow-initialize () | ||
| 799 | (if (null shadow-homedir) | ||
| 800 | (setq shadow-homedir | ||
| 801 | (file-name-as-directory (shadow-expand-file-name "~")))) | ||
| 802 | (if (null shadow-info-file) | ||
| 803 | (setq shadow-info-file | ||
| 804 | (shadow-expand-file-name "~/.shadows"))) | ||
| 805 | (if (null shadow-todo-file) | ||
| 806 | (setq shadow-todo-file | ||
| 807 | (shadow-expand-file-name "~/.shadow_todo"))) | ||
| 808 | (if (not (shadow-read-files)) | ||
| 809 | (progn | ||
| 810 | (message "Shadowfile information files not found - aborting") | ||
| 811 | (beep) | ||
| 812 | (sit-for 3)) | ||
| 813 | (shadow-when (and (not shadow-inhibit-overload) | ||
| 814 | (not (fboundp 'shadow-orig-save-buffers-kill-emacs))) | ||
| 815 | (fset 'shadow-orig-save-buffers-kill-emacs | ||
| 816 | (symbol-function 'save-buffers-kill-emacs)) | ||
| 817 | (fset 'save-buffers-kill-emacs | ||
| 818 | (symbol-function 'shadow-save-buffers-kill-emacs))) | ||
| 819 | (add-hook 'write-file-hooks 'shadow-add-to-todo) | ||
| 820 | (define-key ctl-x-4-map "s" 'shadow-copy-files))) | ||
| 821 | |||
| 822 | (if noninteractive | ||
| 823 | (add-hook 'after-init-hook 'shadow-initialize) | ||
| 824 | (shadow-initialize)) | ||
| 825 | |||
| 826 | ;;;Local Variables: | ||
| 827 | ;;;eval:(put 'shadow-when 'lisp-indent-hook 1) | ||
| 828 | ;;;End: | ||
| 829 | |||
| 830 | ;;; shadowfile.el ends here | ||