1 ;;; tinydesk.el --- Save and restore files between Emacs sessions
3 ;; This file is not part of Emacs
7 ;; Copyright (C) 1995-2007 Jari Aalto
8 ;; Keywords: extensions
10 ;; Maintainer: Jari Aalto
12 ;; To get information on this program, call M-x tinydesk-version.
13 ;; Look at the code with folding.el
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)
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
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.
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
37 ;; ........................................................ &t-install ...
38 ;; Put this file on your Emacs-Lisp load path, add following into your
39 ;; $HOME/.emacs startup file
41 ;; (add-hook 'tinydesk-:load-hook 'tinydesk-default-emacs-keybindings)
42 ;; (add-hook 'tinydesk-:load-hook 'tinydesk-recover-last-state)
43 ;; (require 'tinydesk)
45 ;; or use the autoload feature. Notice that the automatic "file
46 ;; state backup feature" gets enables only when this file is loaded.
47 ;; If you want that feature, then use require.
49 ;; (add-hook 'tinydesk-:load-hook 'tinydesk-default-emacs-keybindings)
50 ;; (add-hook 'tinydesk-:load-hook 'tinydesk-recover-last-state)
51 ;; (autoload 'tinydesk-mode "tinydesk" "" t)
52 ;; (autoload 'tinydesk-save-state "tinydesk" "" t)
53 ;; (autoload 'tinydesk-unload "tinydesk" "" t)
54 ;; (autoload 'tinydesk-recover-state "tinydesk" "" t)
55 ;; (autoload 'tinydesk-edit-state-file "tinydesk" "" t)
57 ;; Suggested keybindings. These are inlcuded in function
58 ;; `tinydesk-default-keybindings'.
60 ;; (define-key ctl-x-4-map "S" 'tinydesk-save-state)
61 ;; (define-key ctl-x-4-map "R" 'tinydesk-recover-state)
62 ;; (define-key ctl-x-4-map "E" 'tinydesk-edit-state-file)
63 ;; (define-key ctl-x-4-map "U" 'tinydesk-unload)
65 ;; If you have any questions, use this function:
67 ;; M-x tinydesk-submit-bug-report
69 ;; To read the documentation after file has been loaded, call
71 ;; M-x tinydesk-version
76 ;; ..................................................... &t-commentary ...
82 ;; At work working with windowed system, Emacs stays open from day to
83 ;; day. In fact people seldom even logout, so Emacs and the files
84 ;; just wait there nicely and there is seldom a need for a sophisticated
87 ;; But sometimes sometimes it may be necessary to visit lab next
88 ;; floor to see what's troubling a C++ program. There has to be a way
89 ;; to transfer the list of files that was being editing and bring
90 ;; them into lab where person can replicate the setup.
92 ;; These functions save Emacs configuration into file, which can later be
93 ;; opened again in Emacs somewhere else. Later Emacs versions
94 ;; introduced "~/.saves*" files that you may find disturbing occupying
95 ;; your home directory with many files. With this package all the
96 ;; files are grouped in only one "state" state file, which can be reused.
98 ;; Hopefully someone finds use for this also, although there exist
99 ;; much more better desktop savers, which save points, marks and
102 ;; Overview of features
104 ;; o Simple desktop: only filenames and directories are read/saved.
105 ;; Unlike the other desktop savers, this one can also UNLOAD files
106 ;; from Emacs. You just tell it to remove 'these files listed in
107 ;; state file state.XXX', and those files will be removed from
108 ;; your Emacs buffers. You can collect 'projects' and switch
109 ;; between them easily: after project1, It can can be unload and
110 ;; load project3 instead.
112 ;; o Parse any file that includes filenames and comments
114 ;; o If there were any invalid entries in the state file,
115 ;; the state file contents is shown to user and the entries which
116 ;; had problems are marked.
118 ;; o State file editing (tinydesk-mode):
120 ;; -- load single file on the line
121 ;; -- clear face properties from buffer, so that they don't
122 ;; disturb your view.
123 ;; -- parse files for loading.
124 ;; -- Show files that cannot be loaded.
126 ;; o In regular intervals save the state of Emacs (files loaded)
127 ;; If Emacs crashes you can recover the previous session.
128 ;; See function `tinydesk-auto-save' for more. Similar functionality
129 ;; (".saves") is in new Emacs releases, but this package
130 ;; was originally written using 19.28
132 ;; o CRASH RECOVERY: If Emacs crashes, or you have to kill it
133 ;; with `-HUP' if it hangs, it leaves autosaved files around. When
134 ;; you boot up again, you need to reload the existing files AND
135 ;; recover any autosaved files. The best way to get your Emacs
136 ;; back where it was, is that you load the state file for editing:
137 ;; `M-x' `tinydesk-edit-state-file' And from the edit
138 ;; mode hit command `tinydesk-find-file-whole-buffer' which is
139 ;; bound to C-c b and `tinydesk-recover-file-whole-buffer' which
140 ;; is bound to C-c B. And you'll be up again with your latest
145 ;; If you're just eager to use the package, here are the basics.
146 ;; I suppose you have copied the installation setup as is.
148 ;; o You have Emacs session open with bunch of files. Now you
149 ;; believe that it's time to save this session. You do
150 ;; C-x 4 s and give some name "state.c" if you edited c project.
152 ;; Now, it all depends what you want to do after that. If you find more
153 ;; files to Emacs; or kill some unwanted buffers, you can re-execute
154 ;; C-x 4 s whenever you like. You can even edit the state file with
155 ;; C-x 4 e to remove some files that you don't want to include to
158 ;; o Next time you open Emacs you can load any state file with
161 ;; If you want to switch between projects; unload first the current
162 ;; project with C-x 4 u "state.c" and reload some other project
163 ;; with C-x 4 r, eg your current C++ project "state.cc"
165 ;; Automatic one time session saver
167 ;; Some people just want to save the session on exit and reopen it
168 ;; when Emacs starts again. I must say that this is not necessarily
169 ;; the best, because when you start Emacs for some quick job, you
170 ;; don't necessarily want it to load the saved session (loading all
171 ;; files take time considerably). Loading Emacs with -q is not the
172 ;; choice, if you still like to have your other Emacs goodies active.
174 ;; Here is semi-automatic save and restore, put all these lines near
175 ;; the end of your $HOME/.emacs. The setup saves the state when
176 ;; Emacs exists and asks if you want to return to saved session on
177 ;; Emacs startup. (You did also copy the installation lines too...)
179 ;; (defconst tinydesk-:directory-location "~/elisp/config")
181 ;; (defconst my-tinydesk-session
182 ;; (concat tinydesk-:directory-location "/state.last-session"))
184 ;; (add-hook 'kill-emacs-hook 'my-save-session)
186 ;; (defun my-save-session ()
187 ;; "Save loaded files to state file."
188 ;; ;; if you want to save dired buffers too.
189 ;; ;; use (tinydesk-save-state my-tinydesk-session '(4))
190 ;; (tinydesk-save-state my-tinydesk-session) nil)
192 ;; (if (and (file-exists-p my-tinydesk-session)
193 ;; (y-or-n-p "Recover session "))
194 ;; (tinydesk-recover-state my-tinydesk-session))
198 ;; This program uses some faces to catch your attention when you're
199 ;; working with the state files. I you restore state from a file and
200 ;; some file reference cannot be loaded, the state file will be shown
201 ;; to you and the problematic lines are highlighted. If you open the
202 ;; state file for editing, you can selectively load files. The mouse
203 ;; pointer will change and the text is again highlighted. To make the
204 ;; highlight work for you, you must set some colors like this
206 ;; (set-face-foreground 'italic "LightBlue")
208 ;; About saving the files
210 ;; While you may save your session files with any name, here is one
211 ;; convention that you could use. Name every filename so, that they
212 ;; have common prefix:
214 ;; M-x tinydesk-save-state ;; or any hotkey you have bound this to
217 ;; The XXX describes the name of the state file you just saved. Later
218 ;; on it's easier to use Emacs file name completion capability to load
219 ;; the file you want. If you don't exactly remember what files you
220 ;; saved, or which sessions you have in dir, you just type
224 ;; when `tinydesk-recover-state' ask for filename.
225 ;; Prefix arg to `tinydesk-save-state saves' says to load directories too.
227 ;; Automatic state file saving
229 ;; Emacs 19.29+ has feature that makes it possible to recover a session.
230 ;; See bunch of `auto-save-list-*' variables.
232 ;; Has it ever happened to you that Emacs crashed mystically when you
233 ;; were in the middle of your daily routines. You had several C++
234 ;; files open, perl code, text files, RMAIL, ... This package installs
235 ;; `tinydesk-auto-save' function to `write-file-hooks' and in regular
236 ;; intervals all your Emacs session files are stored into the state
237 ;; file. After a crash you can easily recover your session by reading
238 ;; the saved state file information with `tinydesk-recover-state'
239 ;; <FILE>. The name of the file of the latest saved state is in file
244 ;; There is no plan to duplicate *desktop.el* functionality to save points
245 ;; and modes and so on. This is for simple state restoring only.
255 (eval-when-compile (ti::package-use-dynamic-compilation))
257 (ti::package-defgroup-tiny TinyDesk tinydesk-: extensions
258 "Simple desktop: only filenames and directories are read/saved.
260 Unlike the other desktop savers, this one can also UNLOAD files
261 from Emacs. You just tell it to remove 'these files listed in
262 state file state.XXX', and those files will be removed from
263 your Emacs buffers. You can collect 'projects' and switch
264 between them easily: after project1, It can can
265 be unload and load project3 instead.
267 Files that have been modified are not unloaded.
272 ;;; ......................................................... &v-hooks ...
274 (defcustom tinydesk-:mode-define-keys-hook
275 '(tinydesk-default-mode-bindings)
276 "*List of functions to run which define keys to `tinydesk-mode-map'."
280 (defcustom tinydesk-:load-hook nil
281 "*Hook run when file has been loaded."
285 (defcustom tinydesk-:save-before-hook nil
286 "*Hook run just before _writing_ files to STATE file.
287 begins. This is your chance to do something to the buffers."
291 (defcustom tinydesk-:save-after-hook nil
292 "*Hook just before _saving_ of STATE file.
293 The files are there, possibly in sorted order, and the title is there."
297 (defcustom tinydesk-:mode-hook nil
298 "*Hook run after the `tinydesk-mode' is turned on."
302 (defcustom tinydesk-:recover-before-hook nil
303 "*Hook run after recover file is loaded, just before processing start."
307 (defcustom tinydesk-:recover-after-hook nil
308 "*Hook run after recover file is _parsed_ AND no errors during load."
313 ;;{{{ setup: user config
315 ;;; ................................................... &v-user-config ...
317 (defcustom tinydesk-:comment-characters ";#"
318 "*A string containing comment start characters in state file.
319 The default value is ';#'."
323 (defcustom tinydesk-:mode-name "TinyDesk"
324 "*Editing STATE files mode name."
328 (defcustom tinydesk-:directory-location
330 (file-name-as-directory
331 (file-name-directory (ti::package-config-file-prefix "tinydesk.el")))
332 (and (file-directory-p "~/tmp")
335 TinyDesk: Can't set default value for `tinydesk-:directory-location'"))
336 "*Default directory where to save and restore files."
340 (defcustom tinydesk-:directory-save-suggested 'default
341 "*How the state file's directory location is suggested.
342 'last Offer last saved directory.
343 'default Always offer the default directory `tinydesk-:directory-location'"
349 (defcustom tinydesk-:auto-save-interval 5
350 "*Interval between doing auto-save of Emacs state.
351 If set to 5, after every 5th `write-file' the state is saved.
353 The interval cannot be smaller than 5. It is reseted to 10 if
356 See variable `tinydesk-:auto-save-name-function' and
357 function `tinydesk-auto-save' for more information."
358 :type '(integer :tag "Save interval")
361 (defcustom tinydesk-:auto-save-name-function nil
362 "*Function to return a full path name for auto-save file.
363 If this variable is nil, then default name is derived from frame's
364 first element and it used in `tinydesk-:directory-location'
366 For full documentation, see function `tinydesk-auto-save'"
370 (defcustom tinydesk-:save-exclude-regexp
373 ;; Do save mail buffers; because you want to call M-x rmail
378 ;; No ange ftp buffers
380 "\\(ftp\\|anonymous\\)@.*/"
382 ;; No files from these directories
384 "\\|^/tmp/\\|/junk/\\|/trash/\\|/[aA]utosaved?/")
385 "*Regexp of files that are not saved to state file.
386 match is case sensitive. If you do want not case sensitive match, you
387 have to do set this variable to nil and use your own line delete:
389 (setq tinydesk-:save-after-hook 'my-tinydesk-:save-after-hook)
390 (defun my-tinydesk-:save-after-hook ()
391 (flush-lines REGEXP))"
393 :type '(string :tag "Regexp")
396 (defcustom tinydesk-:save-title
400 ";; Emacs tinydesk.el state file\n"
403 ";; M-x load-library RET tinydesk RET\n"
404 ";; M-x tinydesk-version RET <<to read manual>>\n"
405 ";; M-x tinydesk-recover-state RET %s RET"
407 (ti::date-standard-date 'short)
409 file ;; visible in function `tinydesk-save-state'
411 "*A lisp form to return a string to the beginning of state file."
415 (defcustom tinydesk-:save-and-sort t
416 "*Non-nil to sort the file list in state file.
417 nil to preserve `buffer-list' order."
421 ;; Set to nil if you don't want title.
423 (defcustom tinydesk-:get-save-file-function 'tinydesk-get-save-files
424 "*Function to return list of filenames that are stored to state file.
425 This function isn't run if `tinydesk-save-state' is explicitely
426 run with parameter FILES.
428 Arguments passed to function:
429 mode flag passed by `tinydesk-save-state'"
434 (defcustom tinydesk-:face-table
435 '((file-pick . highlight)
437 "*Alist of faces used for marking text.
440 \(setq tinydesk-:face-table
441 '((file-pick . highlight)
446 (symbol :tag "Face"))
449 (symbol :tag "Face")))
453 ;;{{{ setup: -- private
455 ;;; ....................................................... &v-private ...
457 (defvar tinydesk-mode-map nil
458 "Local keymap for STATE files loaded by edit.")
460 (defvar tinydesk-:directory-last nil
461 "Directory that was used for last save.")
463 (defvar tinydesk-:tmp-buffer "*tmp*"
464 "The work buffer used, created and killed when needed.")
466 (defvar tinydesk-:trash-tmp-buffer t
467 "If non-nil, the work buffer is always deleted.")
469 (defvar tinydesk-:message-column 60
470 "Column where to put possible messages regarding file.")
472 (defvar tinydesk-:auto-save-counter 0
473 "Counter incremented every every time `write-file' event happens.
474 See. `tinydesk-auto-save'.")
476 (defconst tinydesk-:loaded-file-list nil
477 "Overwritten when files are loaded. List.
478 Contain files that were loaded by `tinydesk-find-file-whole-buffer'.
479 Hooks may check the contents of this.")
481 (defconst tinydesk-:rejected-file-list nil
482 "Overwritten when files are loaded. List.
483 Contain files that were *not* loaded by
484 `tinydesk-find-file-whole-buffer'. Reason may be anything: incorrect filename,
485 path, garbage at line...Hooks may check the contents of this.")
487 (defconst tinydesk-:comment-start-level 1
488 "Which sub expression is the comment start.")
490 (defvar tinydesk-:last-state-file nil
491 "Last state file loaded is stored here.")
494 ;;{{{ setup: -- version
496 ;;; ....................................................... &v-version ...
498 ;;;###autoload (autoload 'tinydesk-version "tinydesk" "Display commentary." t)
500 (ti::macrof-version-bug-report
504 "$Id: tinydesk.el,v 2.52 2007/05/06 23:15:19 jaalto Exp $"
505 '(tinydesk-version-id
506 tinydesk-:mode-define-keys-hook
509 tinydesk-:save-before-hook
510 tinydesk-:save-after-hook
512 tinydesk-:recover-before-hook
513 tinydesk-:recover-after-hook
514 tinydesk-:directory-last
516 tinydesk-:trash-tmp-buffer
517 tinydesk-:message-column
518 tinydesk-:loaded-file-list
519 tinydesk-:rejected-file-list
520 tinydesk-:comment-start-level
522 tinydesk-:directory-save-suggested
523 tinydesk-:save-exclude-regexp
524 tinydesk-:comment-characters
526 tinydesk-:save-and-sort
527 tinydesk-:face-table)))
530 ;;{{{ Install: bindings
532 ;;; ----------------------------------------------------------------------
534 (defun tinydesk-default-emacs-keybindings ()
535 "Install package under `ctl-x-4-map'
538 (define-key ctl-x-4-map "S" 'tinydesk-save-state) ;; free in Emacs
539 ;; This was find-file-read-only-other-window
540 (define-key ctl-x-4-map "R" 'tinydesk-recover-state) ;; Not free
541 (define-key ctl-x-4-map "E" 'tinydesk-edit-state-file) ;; free in Emacs
542 (define-key ctl-x-4-map "U" 'tinydesk-unload)) ;; free in Emacs
544 ;;; ----------------------------------------------------------------------
546 (defun tinydesk-default-mode-bindings ()
547 "Define default key bindings to `tinydesk-mode-map'."
549 ;; - Don't want to use mouse-2 because it's for PASTE.
550 ;; - The others are put to mouse-2 because there is not
551 ;; not always 3 button mouse available.
553 (define-key tinydesk-mode-map [mouse-3] 'tinydesk-mouse-load-file)
555 ;; - When editing a file, those colors might be too annoyinng,
556 ;; so you can remove properties with this. Loading is disabled too
557 ;; - Remeber, Emacs is slow with this... wait some time.
559 (define-key tinydesk-mode-map [S-mouse-2]
560 'tinydesk-clear-buffer-properties)
562 ;; To make buffer loadable by mouse again, run this
564 (define-key tinydesk-mode-map [C-mouse-2]
565 'tinydesk-mark-buffer-loadable)
567 ;; To mark files that are not loadable, check for possibly typo in
570 (define-key tinydesk-mode-map [C-M-mouse-2]
571 'tinydesk-set-face-non-files-buffer))
575 (define-key tinydesk-mode-map [(button3)]
576 'tinydesk-mouse-load-file)
578 (define-key tinydesk-mode-map [(shift button2)]
579 'tinydesk-clear-buffer-properties)
581 (define-key tinydesk-mode-map [(control button2)]
582 'tinydesk-mark-buffer-loadable)
584 (define-key tinydesk-mode-map [(control alt button2)]
585 'tinydesk-set-face-non-files-buffer))
587 ;; ............................................. users with no mouse ...
589 (define-key tinydesk-mode-map "\C-c\C-m" 'tinydesk-load-file)
591 (define-key tinydesk-mode-map "\C-cc" 'tinydesk-clear-buffer-properties)
592 (define-key tinydesk-mode-map "\C-cl" 'tinydesk-mark-buffer-loadable)
593 (define-key tinydesk-mode-map "\C-cn" 'tinydesk-set-face-non-files-buffer)
594 (define-key tinydesk-mode-map "\C-cu" 'tinydesk-unload-current-file)
595 (define-key tinydesk-mode-map "\C-cx" 'tinydesk-expunge-unloaded)
596 (define-key tinydesk-mode-map "\C-cr" 'tinydesk-remove-file-coments)
597 (define-key tinydesk-mode-map "\C-cb" 'tinydesk-find-file-whole-buffer)
598 (define-key tinydesk-mode-map "\C-cB" 'tinydesk-recover-file-whole-buffer))
603 ;;; ----------------------------------------------------------------------
605 (defsubst tinydesk-comment ()
610 (substring tinydesk-:comment-characters 0 1 ))))
612 ;;; ----------------------------------------------------------------------
614 (defsubst tinydesk-comment-re ()
615 "Return comment regexp."
617 tinydesk-:comment-characters
619 tinydesk-:comment-characters
622 ;;; ----------------------------------------------------------------------
624 (defsubst tinydesk-read-word ()
625 "Read filename word."
626 ;; Windows use spaces in file names
627 (ti::remove-properties
628 (ti::string-remove-whitespace
629 (ti::buffer-read-word "- a-zA-Z0-9_/.!@#%&{}[]+:;~`<>"))))
631 ;;; ----------------------------------------------------------------------
633 (defsubst tinydesk-tmp-buffer (&optional clear)
634 "Return temp buffer. optionally CLEAR it."
635 (ti::temp-buffer tinydesk-:tmp-buffer clear))
637 ;;; ----------------------------------------------------------------------
639 (defsubst tinydesk-file-name-absolute (file)
640 "Add `default-directory' to FILE if it has no directory."
642 (if (not (string-match "/" (or file "")))
643 (setq file (concat default-directory file))))
646 ;;; ----------------------------------------------------------------------
648 (defsubst tinydesk-mode-map-activate ()
649 "Use local \\{tinydesk-mode-map} on this buffer."
650 (use-local-map tinydesk-mode-map))
652 ;;; ----------------------------------------------------------------------
654 (defun tinydesk-get-save-dir ()
655 "Return suggested save directory."
656 (let* ((type tinydesk-:directory-save-suggested)
657 (last tinydesk-:directory-last)
658 (dir tinydesk-:directory-location)
659 (ret dir)) ;set default return value
660 (if (and (eq type 'last)
662 (file-writable-p last))
668 ;;; ----------------------------------------------------------------------
670 (defun tinydesk-only-files-buffer ()
671 "Remove all comments and empty lines from buffer and leave 1st word."
673 (tinydesk-only-files-region (point-max) (point-min)))
675 ;;; ----------------------------------------------------------------------
677 (defun tinydesk-only-files-region (beg end)
678 "Remove comments BEG END and empty lines from region and leave 1st word.
679 This way you can rip off all comments and leave filenames."
681 (let* ((sub-level tinydesk-:comment-start-level)
682 (comment-re (tinydesk-comment-re))
683 (empty-re "^[ \t]*$\\|$")
690 (setq tmp beg beg end end tmp))
693 ;; markers has to be used, beacuse we delete lines and points move
694 (setq mark-end (point-marker))
696 (while (< (point) (marker-position mark-end))
697 (setq p (point) maxp nil)
699 (if (null (looking-at empty-re))
701 (ti::buffer-kill-line)
703 (if (null (looking-at comment-re))
705 (if (match-beginning sub-level)
706 (setq maxp (match-beginning sub-level))))
707 (if (and maxp (eq maxp p)) ;BEG of line comment
709 (ti::buffer-kill-line) (throw 'next t)))
710 (setq word (tinydesk-read-word))
711 ;;; (setq word (tinydesk-read-word p maxp))
712 (ti::buffer-kill-line)
713 ;; The \n make cursor forward
715 (insert word "\n")))))))
717 ;;; ----------------------------------------------------------------------
719 (defun tinydesk-trash ()
720 "Kill temporary buffer if user has requested it.
723 `tinydesk-:tmp-buffer'
724 `tinydesk-:trash-tmp-buffer'"
725 (and tinydesk-:trash-tmp-buffer
726 (get-buffer tinydesk-:tmp-buffer)
727 (kill-buffer (get-buffer tinydesk-:tmp-buffer))))
729 ;;; ----------------------------------------------------------------------
731 (defun tinydesk-dired-table ()
732 "Return dired buffer table '((directory dired-buffer) ...)."
734 (let ((blist (buffer-list))
735 ;; ByteCompiler doesn't know that I do
736 ;; (eq major-mode 'dired-mode) test before I use this variable,
737 ;; so hide it from it.
739 ;; The variable is defined if it passed the eq test
740 (sym 'dired-directory)
743 (with-current-buffer elt
744 (if (eq major-mode 'dired-mode)
746 (list (symbol-value sym)
752 ;;{{{ code: auto save
754 ;;; ----------------------------------------------------------------------
756 (defun tinydesk-auto-save-file-name ()
757 "Return state file name for auto save. See function `tinydesk-auto-save'.
759 `tinydesk-:directory-location'
760 `tinydesk-:auto-save-name-function'."
761 (let* ((func tinydesk-:auto-save-name-function)
762 (dir (or tinydesk-:directory-location "~" ))
763 (name (or (and (boundp 'command-line-args)
764 (nth 1 (member "-name" command-line-args)))
767 (file-name-as-directory dir)
768 "emacs-config-tinydesk-autosave-"
769 (or name "saved"))) ;; default
772 (setq save-to (funcall func)))
775 ;;; ----------------------------------------------------------------------
777 (defun tinydesk-auto-save (&optional force)
778 "This function is installed in `write-file-hooks'. Periodic auto save.
782 FORCE Do autosave immediately
784 Every Nth time the state of the Emacs (which files were loaded into Emacs)
785 is saved, so that you can recover the same session if Emacs crashes.
787 The default state name is derived in the following manner
789 o use directory `tinydesk-:directory-location'
790 o add string \"emacs-tinydesk-autosave-\"
791 o get frame's first word, usually the one that gets set when
792 you use -name XXX switch in Emacs command line. If Emacs is being
793 run with -nw option, the frame name returns \"terminal\"
794 o if there is no frame name, then use \"periodic\"
796 Possible yielding: ~/elisp/config/state.saved
800 `tinydesk-:auto-save-counter'
801 `tinydesk-:auto-save-interval' every Nth write"
803 (let* ((backup-inhibited t)
804 (save-to (tinydesk-auto-save-file-name)))
805 (when (stringp save-to)
806 ;; - Be extra careful, because we're in write file hook
807 ;; - Make sure we always succeed
808 (if (not (integerp tinydesk-:auto-save-counter)) ;; init if not int
809 (setq tinydesk-:auto-save-counter 0))
810 (if (not (integerp tinydesk-:auto-save-interval)) ;; user didn't set this?
811 (setq tinydesk-:auto-save-interval 10))
812 (if (< tinydesk-:auto-save-interval 5) ;; Must be more than 5
813 (setq tinydesk-:auto-save-interval 10))
814 (incf tinydesk-:auto-save-counter)
815 ;; time's up? Select name if it's string.
818 (> tinydesk-:auto-save-counter tinydesk-:auto-save-interval))
819 ;; Actually tinydesk-save-state generates new call to this
820 ;; function but, it won't come in this COND, because the counter
821 ;; value is different.
822 (setq tinydesk-:auto-save-counter 0)
823 ;; Try chmod, if it fails, then signal error
824 (if (and (file-exists-p save-to)
825 (not (file-writable-p save-to)))
828 (ti::file-mode-make-writable (file-modes save-to))))
829 ;; Still no luck after chmod ?
830 (if (or (not (file-directory-p (file-name-directory save-to)))
831 (and (file-exists-p save-to)
832 (not (file-writable-p save-to))))
834 TinyDesk: Can't do state autosave: [%s] is not writable." save-to))
836 (save-window-excursion
838 (message "TinyDesk: state backup in file %s" save-to)
839 (tinydesk-save-state save-to)))))
840 ;; `write-file-hook' function must return nil
847 ;;; ----------------------------------------------------------------------
849 (defun tinydesk-face (face)
851 ;; This way the global variable does not float around the file
852 (cdr (assoc face tinydesk-:face-table)))
854 ;;; ----------------------------------------------------------------------
856 (defun tinydesk-clear-line-properties ()
857 "Remove properties from the line."
858 (set-text-properties (line-beginning-position) (line-end-position) nil)
859 (set-buffer-modified-p nil))
861 ;;; ----------------------------------------------------------------------
863 (defun tinydesk-clear-buffer-properties ()
864 "Remove properties and EOL comments from buffer."
866 (let* ((c-chars tinydesk-:comment-characters)
867 (c-lev tinydesk-:comment-start-level)
868 (c-re (tinydesk-comment-re)))
869 (tinydesk-clear-region-properties (point-min) (point-max))
874 ;; Skip over BOL comments
875 (if (and (not (looking-at (concat "^[ \t]*[" c-chars "]+\\|$")))
877 (match-beginning c-lev))
878 (delete-region (match-beginning c-lev) (line-end-position)))
880 (set-buffer-modified-p nil)
881 (message "TinyDesk: *properties cleared from buffer")
882 ;; Little nasty, but Emacs does not update display always...
885 ;;; ----------------------------------------------------------------------
887 (defun tinydesk-clear-region-properties (beg end)
888 "Remove properties from BEG END."
889 (set-text-properties beg end nil)
890 (set-buffer-modified-p nil))
892 ;;; ----------------------------------------------------------------------
894 (defun tinydesk-line-property-set-error ()
895 "Set line face to signify error."
899 (beginning-of-line) (setq beg (point))
900 (skip-chars-forward "^ \t\n") (setq end (point)))
901 ;; clear first full line
902 (put-text-property beg (line-end-position) 'face 'default)
903 (put-text-property beg end 'face (tinydesk-face 'error))
904 (set-buffer-modified-p nil)))
906 ;;; ----------------------------------------------------------------------
908 (defun tinydesk-handle-text-property (p text)
909 "Look property P and run command under TEXT."
910 (let* ((file (file-name-nondirectory text))
911 (loaded (get-buffer file)) ;in Emacs already ?
912 (comment (tinydesk-comment))
913 (err-col tinydesk-:message-column))
914 ;; We need the sleep-for, because moving the mouse
915 ;; clears the message and user may not notice it.
917 ((eq p (tinydesk-face 'file-pick))
919 ((null (file-exists-p text))
920 (message (concat "TinyDesk: File not exist, " text)) (sleep-for 0)
921 (tinydesk-clear-line-properties))
923 (message "TinyDesk: File already loaded.") (sleep-for 0)
924 (tinydesk-clear-line-properties))
926 (find-file-noselect text)
927 (message "TinyDesk: Loaded ok.") (sleep-for 0)
928 (tinydesk-clear-line-properties)
929 (move-to-column err-col t)
930 (if (not (looking-at "$\\|[ \t]*$"))
932 (insert comment " loaded")
933 (beginning-of-line)))))))
935 ;;; ----------------------------------------------------------------------
937 (defun tinydesk-mark-buffer-loadable (&optional verb)
938 "Parse whole buffer and make first _word_ loadable with mouse. VERB.
939 Marking is only done if word is valid filename."
942 (tinydesk-mark-region
943 (point-min) (point-max)
944 (tinydesk-comment-re)
945 tinydesk-:comment-start-level
949 ;;; ----------------------------------------------------------------------
951 (defun tinydesk-set-face-non-files-buffer ()
952 "Change face to 'error in those lines whose first word is not valid file."
954 (tinydesk-set-face-non-files-region (point-min) (point-max))
956 (message "TinyDesk: marked non-lodable files")))
958 ;;; ----------------------------------------------------------------------
960 (defun tinydesk-set-face-non-files-region (beg end)
961 "Change face to 'error in those lines whose first word is not valid file.
962 Also add comment marker for user that do not have highlight capability.
965 (let* ((empty-re "^[ \t]*$")
966 (sub-level tinydesk-:comment-start-level)
967 (c-chars tinydesk-:comment-characters)
968 (comment (tinydesk-comment))
969 (comment-re (tinydesk-comment-re))
970 (err-col tinydesk-:message-column)
974 (narrow-to-region beg end)
977 ;; - ignore empty lines and BEG of line comments.
978 (if (or (looking-at empty-re)
979 (and (looking-at comment-re)
980 (eq (match-beginning sub-level) (point))))
982 (setq word (tinydesk-read-word))
983 (if (and word (file-exists-p word))
986 (tinydesk-line-property-set-error) ;put color on line
987 ;; Show to user that does not see the color
988 (move-to-column err-col t)
989 ;; Is the filename that long, that it goes past err-col ?
991 ((eq (point) (line-end-position))) ;do nothing
992 ((looking-at (concat "[ \t" c-chars "]"))
993 (kill-line)) ;delete other marks
994 (t ;no other choices. place is cccupied
996 (insert " "))) ;separate word
997 (insert (concat comment " invalid"))))
998 (forward-line 1))))))
1000 ;;; ----------------------------------------------------------------------
1001 ;;; - This function is not general.
1002 ;;; #todo: rewrite it for this module only
1004 (defun tinydesk-mark-region (beg end &optional com-re sub-level verb)
1005 "Make all filenames in the buffer loadable by mouse.
1006 Supposes that the first _word_ on the line is filename.
1007 If the first word isn't loadable file, its face isn't changed.
1008 If there is no directory part, then `default-directory' is supposed.
1014 COM-RE the file can have comments, but comments can be only
1015 _single span type_, that is, only shell like #, or C++
1016 like //. Everything after and included COM-RE is discarded
1019 SUB-LEVEL subexpression match; default is 0.
1021 VERB verbose messages.
1027 com-re = '.*\\\\(#\\\\)'
1028 sub-level = 1 , because there is paren"
1029 (let* ((err-col tinydesk-:message-column)
1030 (file-face (tinydesk-face 'file-pick))
1031 (sub-level (or sub-level 0))
1032 (c-chars tinydesk-:comment-characters)
1033 (comment (tinydesk-comment))
1034 bp ep ;beg, end points
1036 maxlp ;max line point
1038 (and verb ;this make take a while...
1039 (message "TinyDesk: Marking files..."))
1041 (narrow-to-region beg end)
1042 (goto-char (point-min))
1044 (if (looking-at "^[ \t]*$\\|$")
1045 nil ;ignore empty lines
1046 (setq elp (line-end-position))
1048 ;; Does there exist comment on the line ?
1050 (when (and (stringp com-re)
1051 (looking-at com-re))
1052 (setq maxlp (1- (match-beginning sub-level)))))
1053 (if (< maxlp (1+ (point))) ;beg of line COMMENT exist
1055 ;;; (ti::d! "skipped" (ti::read-current-line))
1057 (skip-syntax-forward " " maxlp) ;ignore whitespace
1060 (skip-chars-forward "^ \t" maxlp) ;first space
1061 (if (eq bp ep) ;not moved, maybe "one-word$" ?
1065 nil ;still not moved ?
1066 ;; Mark the word only if the WORD is valid file
1067 ;; - If the filename has ange-ftp @ char, then mark
1068 ;; automatically. Calling file-exists-p for ange
1069 ;; file would start ange-ftp... and we don't
1071 (setq file (buffer-substring bp ep))
1072 (setq file (tinydesk-file-name-absolute file))
1073 ;;; (ti::d! bp ep (point) file )
1075 (if (looking-at "[ \t;]")
1076 (delete-region (point) elp)) ;delete other marks
1078 ((get-file-buffer file) ;already in Emacs ?
1079 (move-to-column err-col t)
1080 (if (not (looking-at (concat "$\\|[ \t" c-chars "]")))
1081 (end-of-line)) ;no other choices, place is cccupied
1082 (insert (concat comment " loaded")))
1084 ;; ............................... not loaded in Emacs ...
1085 (if (or (string-match "@" file)
1086 (file-exists-p file))
1087 (put-text-property bp ep 'mouse-face file-face)))))))
1089 (set-buffer-modified-p nil)
1090 (and verb ;this make take a while...
1091 (message "TinyDesk: Marking files...ok")))))
1096 ;;; ........................................................... &mouse ...
1098 ;;; ----------------------------------------------------------------------
1100 (defun tinydesk-mouse-load-file (event)
1101 "Load file under mouse. Use mouse EVENT."
1103 (mouse-set-point event) ;move point there
1104 (tinydesk-load-file))
1106 ;;; ----------------------------------------------------------------------
1108 (defun tinydesk-remove-file-coments ()
1109 "Remove all comment at `tinydesk-:message-column'."
1111 (ti::save-line-column-macro nil nil
1113 (while (re-search-forward (tinydesk-comment) nil t)
1114 (if (eq (current-column) (+ 2 tinydesk-:message-column))
1115 (delete-region (- (point) 2) (line-end-position))))))
1117 ;;; ----------------------------------------------------------------------
1119 (defun tinydesk-expunge-unloaded ()
1120 "Remove lines that have 'unloaded' flag."
1122 (ti::save-line-column-macro nil nil
1124 (while (re-search-forward (format "%s unloaded$" (tinydesk-comment)) nil t)
1125 (ti::buffer-kill-line))))
1127 ;;; ----------------------------------------------------------------------
1129 (defun tinydesk-unload-current-file ()
1130 "Remove file on this line from Emacs."
1132 (let* ((file (tinydesk-file-name-absolute
1133 (tinydesk-read-word)))
1136 (if (setq buffer (find-buffer-visiting (expand-file-name file)))
1137 (kill-buffer buffer)
1138 (message "TinyDesk: No such buffer."))
1140 (or (and (re-search-forward (tinydesk-comment) nil t)
1141 (progn (delete-region (point) (line-end-position)) t))
1142 (and (move-to-column tinydesk-:message-column t)
1145 (insert " unloaded")
1148 ;;; ----------------------------------------------------------------------
1150 (defun tinydesk-load-file ()
1151 "Load file under point."
1155 (setq prop (get-text-property (point) 'mouse-face))
1157 (prop ;property found?
1158 (setq word (tinydesk-file-name-absolute
1159 (tinydesk-read-word))) ;read word under cursor
1162 (tinydesk-handle-text-property prop word))))
1165 (substitute-command-keys
1167 "TinyDesk: Can't find mouse face... Mark buffer first with "
1168 "\\[tinydesk-mark-buffer-loadable]")))))))
1170 ;;{{{ code: edit, unload
1172 ;;; ----------------------------------------------------------------------
1175 (defun tinydesk-unload (file &optional verb)
1176 "Unload all files from Emacs that are in state file FILE.
1178 If VERB is non-nil offer verbose messages [for code calls]; interactive
1179 call always turns on verbose."
1183 (let* ((save-dir (or (tinydesk-get-save-dir) "~/"))
1184 (msg (concat "Unload from state file: ")))
1185 (read-file-name msg save-dir))))
1186 (let* ((b (tinydesk-tmp-buffer))
1187 (dlist (tinydesk-dired-table))
1194 (with-current-buffer b
1196 (insert-file-contents file)
1197 ;; - Now process every line. We don't care if we read commented
1198 ;; line as "buffer" because the test inside loop return nil
1199 ;; for such lines...
1203 (message "TinyDesk: Empty state file."))
1206 ;; - Expect Win32 or Unix absolute path name in line
1207 ;; - find-buffer-visiting function can find files that
1210 (when (and (looking-at "[a-zA-Z]:/[^ \t\n\r]+\\|[~/][^ \t\n\r]+")
1211 (setq fn (match-string 0))
1212 (or (and (file-directory-p fn)
1213 (setq elt (assoc (expand-file-name fn) dlist))
1214 (setq buffer (nth 1 elt)))
1215 (setq buffer (find-buffer-visiting fn))))
1216 (with-current-buffer buffer
1218 ((not (buffer-modified-p))
1219 (kill-buffer (current-buffer))
1222 (message "Tinydesk: Buffer %s modified. Won't unload."
1228 (message (format "TinyDesk: Removed %d/%d files. %s"
1231 (if (equal count total)
1233 " Modified buffer not unloaded.")))
1234 (message "TinyDesk: No files removed.")))
1237 ;;; ----------------------------------------------------------------------
1240 (defun tinydesk-mode (&optional no-face verb)
1241 "Mark and parse buffer's fist words as loada files.
1242 If NO-FACE is non-nil, the default mouse marking isn't performed. VERB.
1244 Comments in the right tell what is the files status:
1245 loaded = file inside Emacs already
1246 invalid = the path is invalid, no such file exists
1250 \\{tinydesk-mode-map}"
1253 ;; - If the file is already in buffer, remove extra marks, like
1254 ;; non-loadable files.
1255 (tinydesk-clear-region-properties (point-min) (point-max))
1256 (tinydesk-remove-file-coments)
1258 (tinydesk-mark-buffer-loadable verb))
1259 (tinydesk-mode-map-activate) ;turn on the map
1260 (setq mode-name tinydesk-:mode-name)
1261 (setq major-mode 'tinydesk-mode) ;; for C-h m
1264 (substitute-command-keys
1266 "load \\[tinydesk-load-file] "
1267 "clear \\[tinydesk-clear-buffer-properties] "
1268 "error \\[tinydesk-set-face-non-files-buffer] "
1269 "mark \\[tinydesk-mark-buffer-loadable]")))
1271 (run-hooks 'tinydesk-:mode-hook))
1273 ;;; ----------------------------------------------------------------------
1275 (defun turn-on-tinydesk-mode ()
1276 "Turn on `tinydesk-mode'."
1280 ;;; ----------------------------------------------------------------------
1282 (defun turn-on-tinydesk-mode-maybe ()
1283 "Turn on `tinydesk-mode' if `tinydesk-:save-title' is found."
1285 (let* ((string (substring
1286 (or (eval tinydesk-:save-title)
1287 "####No-string-available###")
1291 (when (re-search-forward
1292 (concat "^" (regexp-quote string)) nil 'noerr)
1293 (turn-on-tinydesk-mode)))))
1295 ;;; ----------------------------------------------------------------------
1297 (defun turn-off-tinydesk-mode ()
1298 "Turn off `tinydesk-mode'."
1300 (if (functionp default-major-mode)
1301 (funcall default-major-mode)
1302 (fundamental-mode)))
1304 ;;; ----------------------------------------------------------------------
1307 (defun tinydesk-edit-state-file (file)
1308 "Load state FILE into buffer for editing.
1309 You can add comments and remove/add files. Turns on `tinydesk-mode'.
1311 Following commands are available in `tinydesk-mode'.
1312 \\{tinydesk-mode-map}"
1313 ;; I can't use interactive "f" , beacuse I want the completions
1314 ;; to come from the save-directory. The "f" uses by default the
1315 ;; variable default-directory...
1318 (let* ((save-dir (tinydesk-get-save-dir))
1319 (save-dir (if save-dir
1322 (msg (concat "Edit state file: ")))
1323 (read-file-name msg save-dir))))
1324 ;; If file is already loaded, avoid creating duplicate window
1325 (pop-to-buffer (find-file-noselect file))
1326 (tinydesk-mode nil t))
1331 ;;; ............................................................ &save ...
1333 ;;; ----------------------------------------------------------------------
1335 (defun tinydesk-get-save-files (&optional dirs)
1336 "Return list of files to save, optionally DIRS too."
1337 (let ( ;; See function tinydesk-dired-table for explanation
1338 (sym 'dired-directory)
1341 (dolist (elt (buffer-list))
1342 (with-current-buffer elt
1345 (eq major-mode 'dired-mode))
1346 (push (symbol-value sym) list))
1347 ((setq tmp (buffer-file-name))
1351 ;;; ----------------------------------------------------------------------
1354 (defun tinydesk-save-state (file &optional mode files verb)
1355 "Output all files in Emacs into FILE.
1356 Notice, that this concerns only buffers with filenames.
1360 FILE the STATE file being saved
1362 MODE Control what is saved:
1364 '(4) \\[universal-argument], filenames and directories.
1365 '(16) \\[universal-argument] \\[universal-argument], Use absolute paths to HOME.
1367 FILES filenames , absolute ones. If nil then
1368 `tinydesk-:get-save-file-function' is run to get files.
1371 (list (read-file-name "Save state to: " (tinydesk-get-save-dir))
1372 current-prefix-arg))
1373 (let* ((tmp-buffer (tinydesk-tmp-buffer 'clear))
1374 (save-func tinydesk-:get-save-file-function)
1375 (sort tinydesk-:save-and-sort)
1376 (title tinydesk-:save-title)
1377 (re-no tinydesk-:save-exclude-regexp)
1378 (absolute-p (equal mode '(16)))
1381 (setq tinydesk-:directory-last (file-name-directory file))
1382 ;; #todo: Kill buffer if it is not modified and reload it
1384 (when (setq buffer (get-file-buffer file))
1385 (pop-to-buffer buffer)
1387 TinyDesk: State saving aborted. Please save to new file or kill buffer: %s" file ))
1388 (run-hooks 'tinydesk-:save-before-hook)
1390 (setq files (and (fboundp save-func)
1391 (funcall save-func mode))))
1394 (message "TinyDesk: no files to save"))
1395 ;; ... ... ... ... ... ... ... ... ... ... ... ... ... . do save . .
1397 (and (file-exists-p file)
1398 (null (file-name-directory file))))
1399 (error (format "TinyDesk: access problem with: '%s'" file)))
1400 ;; We kill this buffer later, so we don't need save-excursion
1401 (set-buffer tmp-buffer)
1402 ;; ... ... ... ... ... ... ... ... ... ... ... ... insert files . .
1404 ;; Remove some files...
1405 (if (or (not (stringp re-no))
1406 (and (stringp re-no)
1408 (ti::string-match-case re-no elt))))
1409 ;; win32 needs complete path name, not just ~/path/...
1412 ;; Don't try to expand ange-ftp filenames. It would
1413 ;; cause a ftp connections to be opened and that's slow....
1414 (unless (ti::file-name-remote-p elt)
1415 (expand-file-name elt))
1416 (abbreviate-file-name elt))
1418 ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... sort . .
1420 (sort-lines nil (point-min) (point-max)))
1421 ;; ... ... ... ... ... ... ... ... ... ... ... ... ... ... title . .
1424 (insert (eval title)))
1425 (run-hooks 'tinydesk-:save-after-hook)
1426 (write-region (point-min) (point-max) file)
1427 (not-modified) (message "")
1428 (kill-buffer tmp-buffer)
1430 (message (concat "TinyDesk: State saved to file " file)))
1431 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ catch ^^^
1438 ;;; ----------------------------------------------------------------------
1440 (defun tinydesk-rename-buffer-maybe ()
1441 "Rename buffer it FILENAME-DIR if there is <N> in the buffer name.
1442 If two or more of the files are loaded into emacs with the same name
1443 from different directories:
1445 ~/tmp/file.txt => buffer file.txt
1446 ~/txt/file.txt => buffer file.txt<1>
1447 ~/abc/file.txt => buffer file.txt<2>
1450 This function will change the buffer names to include previous
1451 directory part, instead of the <N>, so that the names would read:
1457 (when (and (string-match "<[0-9]+>$" (buffer-name))
1459 (let* ((dir (file-name-directory (buffer-file-name)))
1460 (file (file-name-nondirectory (buffer-file-name)))
1461 (dir1 (replace-regexp-in-string
1463 (or (ti::string-match
1465 ;; Get Two levels up
1466 ".*\\([\\/][^\\/]+[\\/][^\\/]+\\)\\|"
1467 ;; Or one level if only one directory
1468 ".*\\([\\/][^\\/]+\\)")
1472 (rename-buffer (format "%s-%s" file dir1)))))
1474 ;;; ----------------------------------------------------------------------
1476 (defun tinydesk-find-file (file)
1477 "Load FILE or `recover-file' as needed. Rename buffer if buffer<2>"
1478 (with-current-buffer (find-file-noselect file)
1479 (when (and (null (buffer-modified-p))
1480 (file-exists-p (make-auto-save-file-name)))
1481 ;; Can't use (recover-file file), because it asks confirmation.
1482 ;; Emacs should have flag for suppressing questions.
1484 (insert-file-contents-literally (make-auto-save-file-name))
1485 (set-buffer-modified-p t) ;Not strictly needed...
1486 (message "TinyDesk: Recovered file %s"
1487 (make-auto-save-file-name))
1488 (tinydesk-rename-buffer-maybe)
1492 ;;; ----------------------------------------------------------------------
1495 (defun tinydesk-recover-state (file &optional ulp pop verb)
1496 "Load all files listed in FILE into Emacs.
1497 FILE can have empty lines or comments. No spaces allowed at the
1498 beginning of filename. The state FILE itself is not left inside
1499 Emacs if everything loads well. When all files are already
1500 in Emacs, you may see message '0 files loaded'.
1502 In case there were problems, the FILE will be shown and the
1503 problematic lines are highlighted.
1505 Prefix arg sets flag ULP, unload previous.
1509 FILE state file to load
1511 ULP 'unload previous' if non-nil then unload previously
1512 loaded files according to `tinydesk-:last-state-file'
1514 POP if non-nil, then show (pop to) first buffer in saved
1515 state file. This flag is set to t in interactive calls.
1517 VERB non-nil enables verbose messages. This flag is set to
1518 t in interactive calls.
1522 `tinydesk-:last-state-file' Name of state file that was loaded.
1523 `tinydesk-:recover-before-hook' Hook to run before state file processing.
1524 `tinydesk-:recover-after-hook' Hook to run after state file processing."
1527 (read-file-name "Tinydesk: load state file: "
1528 (tinydesk-get-save-dir))
1532 (state-file (expand-file-name file))
1533 (last-state tinydesk-:last-state-file)
1541 ;; o read the config file
1542 ;; o raise the kill flag if the file ISN'T already loaded, user
1543 ;; may be editing it.
1544 ;; o There may be buffers with the same name, but different dirs..
1545 (unless (setq buffer (get-file-buffer state-file))
1546 (setq kill-buffer t) ;different directory
1547 (unless (file-exists-p state-file)
1548 (error "TinyDesk: file does not exist %s" state-file))
1549 (setq buffer (find-file-noselect state-file)))
1550 ;; ... ... ... ... ... ... ... ... ... ... ... ... unload previous ...
1551 (if (and ulp (stringp last-state))
1552 (if (not (file-exists-p last-state))
1554 (format "TinyDesk: Cannot unload, file does not exist '%s' "
1556 (tinydesk-unload last-state)))
1557 (with-current-buffer (ti::temp-buffer buffer)
1558 (setq list (tinydesk-find-file-whole-buffer) ;; before hook
1561 ;; first-entry (nth 3 list)
1562 not-loaded (nth 2 list))
1565 (if verb (message (format "TinyDesk: %d files loaded" count)))
1566 (run-hooks 'tinydesk-:recover-after-hook)
1567 ;; kill the buffer only if it was loaded by us
1569 (kill-buffer buffer)))
1571 ;; Show failed files
1572 (message (concat "TinyDesk: Not loaded> " not-loaded))
1574 (pop-to-buffer buffer)
1575 (tinydesk-mode 'no-face 'verbosee)
1576 (tinydesk-set-face-non-files-buffer)
1578 (setq tinydesk-:last-state-file file))))
1580 ;;; ----------------------------------------------------------------------
1583 (defun tinydesk-recover-last-state ()
1584 "If Emacs was closed / crashed, recover last saved session.
1586 `tinydesk-:auto-save-interval'
1587 `tinydesk-:auto-save-name-function'"
1588 (let ((file (tinydesk-auto-save-file-name)))
1590 (tinydesk-recover-state file)
1592 "TinyDesk: [WARN] Couldn't recover *last* state file."
1593 "function `tinydesk-auto-save-file-name' returned nil")))))
1595 ;;; ----------------------------------------------------------------------
1597 (defun tinydesk-recover-file-whole-buffer (&optional verb)
1598 "Call `tinydesk-find-file' with argument `recover'. VERB."
1601 (tinydesk-find-file-whole-buffer 'recover (ti::verb))))
1603 ;;; ----------------------------------------------------------------------
1605 (defun tinydesk-find-file-whole-buffer (&optional recover verb)
1606 "Load all files listed in buffer. Point is not preserved.
1610 RECOVER Flag. If non-nil, use `recover-file' instead of `find-file'.
1615 `tinydesk-:loaded-file-list'
1616 `tinydesk-:rejected-file-list'
1617 `tinydesk-:recover-before-hook' Hook to run before state file processing.
1621 '(count err not-loaded-string first-entry)
1623 count how many files actually loaded
1624 err error while load
1625 not-loaded-string files that had problems.
1626 first-entry first entry"
1629 (sub-level tinydesk-:comment-start-level)
1630 (ignore-re (tinydesk-comment-re))
1631 (empty-re "^[ \t]*$")
1632 (msg-str (if recover
1638 load ;file ont the line to be processed
1644 (setq tinydesk-:loaded-file-list nil ;<< reset GLOBALS
1645 tinydesk-:rejected-file-list nil)
1646 (run-hooks 'tinydesk-:recover-before-hook)
1647 (ti::pmin) ;there is *no* save excursion
1649 (setq bp (point) err nil) ;BEG of line
1650 (setq maxp (line-end-position))
1653 ;; ... ... ... ... ... ... ... ... ... ... ... ... .. comments ...
1654 (if (looking-at empty-re) ;emty lines
1656 (when (and (looking-at ignore-re)
1657 (match-beginning sub-level))
1658 (setq maxp (match-beginning sub-level)))
1659 (if (eq maxp bp) ;full comment line ?
1661 ;; ... ... ... ... ... ... ... ... ... ... ... read file name ...
1662 ;; Now load the file, raise error if not loaded
1663 ;; Remember that Windows fiels may contain spaces c:\Program Files\
1664 (setq word (tinydesk-read-word))
1665 ;; --``-- --``-- --``-- --``-- --``-- --``-- --``-- --``-- --``--
1667 (setq load (expand-file-name word))
1668 ;;; (ti::d! "buffer?" (get-file-buffer load) (ti::file-find-file-p load) load)
1670 (or load ;file grabbed from line
1671 (not (get-file-buffer load)))) ;already in Emacs
1672 (if (not (ti::file-find-file-p load))
1674 (when (condition-case nil
1677 (message "TinyDesk: %s %s..." msg-str load))
1678 (tinydesk-find-file load)
1683 (setq count (1+ count))
1684 (if (null first-entry)
1685 (setq first-entry word))
1686 (ti::nconc tinydesk-:loaded-file-list word)))))
1687 ;; --``-- --``-- --``-- --``-- --``-- --``-- --``-- --``-- --``--
1690 (push word tinydesk-:rejected-file-list)
1691 (and (interactive-p)
1692 (tinydesk-line-property-set-error))
1695 (or not-loaded "") ;start value
1697 (file-name-nondirectory load)
1702 (message "TinyDesk: %s...done" msg-str))
1703 ;;; (ti::d! "load-end" count ERR not-loaded)
1704 (list count ERR not-loaded first-entry)))
1708 ;;; ----------------------------------------------------------------------
1710 (defun tinydesk-install (&optional uninstall)
1711 "Install or UNINSTALL package."
1713 (unless tinydesk-mode-map
1714 (setq tinydesk-mode-map (make-sparse-keymap))
1715 (run-hooks 'tinydesk-:mode-define-keys-hook))
1718 (remove-hook 'write-file-hooks 'tinydesk-auto-save)
1719 (remove-hook 'find-file-hooks 'turn-on-tinydesk-mode-maybe))
1721 (add-hook 'write-file-hooks 'tinydesk-auto-save)
1722 (add-hook 'find-file-hooks 'turn-on-tinydesk-mode-maybe))))
1727 (run-hooks 'tinydesk-:load-hook)
1729 ;;; tinydesk.el ends here