add sunrise-mode. add hilight-current-line mode. allow meta-shift-up/down to move...
authorJoerg Jaspert <joerg@ganneff.de>
Mon, 8 Apr 2013 12:49:57 +0000 (14:49 +0200)
committerJoerg Jaspert <joerg@ganneff.de>
Mon, 8 Apr 2013 12:49:57 +0000 (14:49 +0200)
16 files changed:
.emacs.d/config/emacs.org
elisp/local/dash.el [new file with mode: 0644]
elisp/local/ganneff.el
elisp/sunrise/.dir-locals.el [new file with mode: 0644]
elisp/sunrise/README.md [new file with mode: 0644]
elisp/sunrise/sunrise-commander.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-buttons.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-checkpoints.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-loop.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-mirror.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-modeline.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-old-checkpoints.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-popviewer.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-tabs.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-tree.el [new file with mode: 0644]
elisp/sunrise/sunrise-x-w32-addons.el [new file with mode: 0644]

index 2e36c9e..6d7c446 100644 (file)
@@ -193,10 +193,11 @@ listed, so I just do it here globally.
 (add-to-list 'load-path "~/elisp/org/contrib")
 (add-to-list 'load-path "~/elisp/tiny")
 (add-to-list 'load-path "~/elisp/mo-git-blame")
-  (add-to-list 'load-path "~/elisp/cedet")
-  (add-to-list 'load-path "~/elisp/cedet/eieio")
-  (add-to-list 'load-path "~/elisp/ecb")
-  (add-to-list 'load-path "~/elisp/jdee/lisp")
+(add-to-list 'load-path "~/elisp/cedet")
+(add-to-list 'load-path "~/elisp/cedet/eieio")
+(add-to-list 'load-path "~/elisp/ecb")
+(add-to-list 'load-path "~/elisp/jdee/lisp")
+(add-to-list 'load-path "~/elisp/sunrise")
 #+END_SRC
 *** Info path
 Help emacs to find the info files
@@ -342,6 +343,11 @@ Try out solarized.
 (load-theme 'solarized-dark t)
 #+END_SRC
 
+*** Hilight current line in buffer
+#+BEGIN_SRC emacs-lisp
+(global-hl-line-mode +1)
+#+END_SRC
+
 *** Allow recursive minibuffers
 This allows (additional) minibuffer commands while in the minibuffer.
 #+BEGIN_SRC emacs-lisp
@@ -402,8 +408,7 @@ Basic settings for emacs integrated shell, using zsh
      (add-to-list 'eshell-command-completions-alist
                   '("gunzip" "gz\\'"))
      (add-to-list 'eshell-command-completions-alist
-                  '("tar" "\\(\\.tar|\\.tgz\\|\\.tar\\.gz\\)\\'"))
-     (add-to-list 'eshell-output-filter-functions 'eshell-handle-ansi-color)))
+                  '("tar" "\\(\\.tar|\\.tgz\\|\\.tar\\.gz\\)\\'"))))
 
 
 #+END_SRC
@@ -471,6 +476,12 @@ Rgrep is infinitely useful in multi-file projects.
   (define-key global-map "\C-x\C-r" 'rgrep)
 #+end_src
 
+Easy way to move a line up - or down. Simpler than dealing with C-x C-t
+AKA transpose lines.
+#+BEGIN_SRC emacs-lisp
+(global-set-key [(meta shift up)]  'move-line-up)
+(global-set-key [(meta shift down)]  'move-line-down)
+#+END_SRC
 **** Overwrite mode
 Usually you can press the *Ins*ert key, to get into overwrite mode. I
 don't like that, have broken much with it and so just forbid it by
@@ -528,6 +539,32 @@ In this day and age, UTF-8 is the way to go.
 (prefer-coding-system 'utf-8)
 #+END_SRC
 
+*** Hilight matching parentheses
+While I do have the nifty shortcut to jump to the other parentheses,
+hilighting them makes it obvious where they are.
+#+BEGIN_SRC emacs-lisp
+(require 'paren)
+(setq show-paren-style 'parenthesis)
+(show-paren-mode +1)
+#+END_SRC
+*** Kill  other bufers
+While many editors allow you to close "all the other files, not the one
+you are in", emacs doesn't have this... Except, now it will.
+#+BEGIN_SRC emacs-lisp
+(require 'dash)
+
+(defun prelude-kill-other-buffers ()
+  "Kill all buffers but the current one.
+Doesn't mess with special buffers."
+  (interactive)
+  (-each
+   (->> (buffer-list)
+     (-filter #'buffer-file-name)
+     (--remove (eql (current-buffer) it)))
+   #'kill-buffer))
+(global-set-key (kbd "C-c k") 'kill-other-buffers)
+
+#+END_SRC
 ** Miscellaneous stuff
 #+BEGIN_SRC emacs-lisp
 (setq backup-by-copying t)
diff --git a/elisp/local/dash.el b/elisp/local/dash.el
new file mode 100644 (file)
index 0000000..1626476
--- /dev/null
@@ -0,0 +1,855 @@
+;;; dash.el --- A modern list library for Emacs
+
+;; Copyright (C) 2012 Magnar Sveen
+
+;; Author: Magnar Sveen <magnars@gmail.com>
+;; Version: 1.1.0
+;; Keywords: lists
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; A modern list api for Emacs.
+;;
+;; See documentation on https://github.com/magnars/dash.el#functions
+
+;;; Code:
+
+(defmacro !cons (car cdr)
+  "Destructive: Sets CDR to the cons of CAR and CDR."
+  `(setq ,cdr (cons ,car ,cdr)))
+
+(defmacro !cdr (list)
+  "Destructive: Sets LIST to the cdr of LIST."
+  `(setq ,list (cdr ,list)))
+
+(defmacro --each (list &rest body)
+  "Anaphoric form of `-each'."
+  (let ((l (make-symbol "list")))
+    `(let ((,l ,list)
+           (it-index 0))
+       (while ,l
+         (let ((it (car ,l)))
+           ,@body)
+         (setq it-index (1+ it-index))
+         (!cdr ,l)))))
+
+(put '--each 'lisp-indent-function 1)
+
+(defun -each (list fn)
+  "Calls FN with every item in LIST. Returns nil, used for side-effects only."
+  (--each list (funcall fn it)))
+
+(defmacro --each-while (list pred &rest body)
+  "Anaphoric form of `-each-while'."
+  (let ((l (make-symbol "list"))
+        (c (make-symbol "continue")))
+    `(let ((,l ,list)
+           (,c t))
+       (while (and ,l ,c)
+         (let ((it (car ,l)))
+           (if (not ,pred) (setq ,c nil) ,@body))
+         (!cdr ,l)))))
+
+(put '--each-while 'lisp-indent-function 2)
+
+(defun -each-while (list pred fn)
+  "Calls FN with every item in LIST while (PRED item) is non-nil.
+Returns nil, used for side-effects only."
+  (--each-while list (funcall pred it) (funcall fn it)))
+
+(defmacro --dotimes (num &rest body)
+  "Repeatedly executes BODY (presumably for side-effects) with `it` bound to integers from 0 through n-1."
+  `(let ((it 0))
+     (while (< it ,num)
+       ,@body
+       (setq it (1+ it)))))
+
+(put '--dotimes 'lisp-indent-function 1)
+
+(defun -dotimes (num fn)
+  "Repeatedly calls FN (presumably for side-effects) passing in integers from 0 through n-1."
+  (--dotimes num (funcall fn it)))
+
+(defun -map (fn list)
+  "Returns a new list consisting of the result of applying FN to the items in LIST."
+  (mapcar fn list))
+
+(defmacro --map (form list)
+  "Anaphoric form of `-map'."
+  `(mapcar (lambda (it) ,form) ,list))
+
+(defmacro --reduce-from (form initial-value list)
+  "Anaphoric form of `-reduce-from'."
+  `(let ((acc ,initial-value))
+     (--each ,list (setq acc ,form))
+     acc))
+
+(defun -reduce-from (fn initial-value list)
+  "Returns the result of applying FN to INITIAL-VALUE and the
+first item in LIST, then applying FN to that result and the 2nd
+item, etc. If LIST contains no items, returns INITIAL-VALUE and
+FN is not called.
+
+In the anaphoric form `--reduce-from', the accumulated value is
+exposed as `acc`."
+  (--reduce-from (funcall fn acc it) initial-value list))
+
+(defmacro --reduce (form list)
+  "Anaphoric form of `-reduce'."
+  (let ((lv (make-symbol "list-value")))
+    `(let ((,lv ,list))
+       (if ,lv
+           (--reduce-from ,form (car ,lv) (cdr ,lv))
+         (let (acc it) ,form)))))
+
+(defun -reduce (fn list)
+  "Returns the result of applying FN to the first 2 items in LIST,
+then applying FN to that result and the 3rd item, etc. If LIST
+contains no items, FN must accept no arguments as well, and
+reduce returns the result of calling FN with no arguments. If
+LIST has only 1 item, it is returned and FN is not called.
+
+In the anaphoric form `--reduce', the accumulated value is
+exposed as `acc`."
+  (if list
+      (-reduce-from fn (car list) (cdr list))
+    (funcall fn)))
+
+(defmacro --filter (form list)
+  "Anaphoric form of `-filter'."
+  (let ((r (make-symbol "result")))
+    `(let (,r)
+       (--each ,list (when ,form (!cons it ,r)))
+       (nreverse ,r))))
+
+(defun -filter (pred list)
+  "Returns a new list of the items in LIST for which PRED returns a non-nil value.
+
+Alias: `-select'"
+  (--filter (funcall pred it) list))
+
+(defalias '-select '-filter)
+(defalias '--select '--filter)
+
+(defmacro --remove (form list)
+  "Anaphoric form of `-remove'."
+  `(--filter (not ,form) ,list))
+
+(defun -remove (pred list)
+  "Returns a new list of the items in LIST for which PRED returns nil.
+
+Alias: `-reject'"
+  (--remove (funcall pred it) list))
+
+(defalias '-reject '-remove)
+(defalias '--reject '--remove)
+
+(defmacro --keep (form list)
+  "Anaphoric form of `-keep'."
+  (let ((r (make-symbol "result"))
+        (m (make-symbol "mapped")))
+    `(let (,r)
+       (--each ,list (let ((,m ,form)) (when ,m (!cons ,m ,r))))
+       (nreverse ,r))))
+
+(defun -keep (fn list)
+  "Returns a new list of the non-nil results of applying FN to the items in LIST."
+  (--keep (funcall fn it) list))
+
+(defmacro --map-when (pred rep list)
+  "Anaphoric form of `-map-when'."
+  (let ((r (make-symbol "result")))
+    `(let (,r)
+       (--each ,list (!cons (if ,pred ,rep it) ,r))
+       (nreverse ,r))))
+
+(defmacro --map-indexed (form list)
+  "Anaphoric form of `-map-indexed'."
+  (let ((r (make-symbol "result")))
+    `(let (,r)
+       (--each ,list
+         (!cons ,form ,r))
+       (nreverse ,r))))
+
+(defun -map-indexed (fn list)
+  "Returns a new list consisting of the result of (FN index item) for each item in LIST.
+
+In the anaphoric form `--map-indexed', the index is exposed as `it-index`."
+  (--map-indexed (funcall fn it-index it) list))
+
+(defun -map-when (pred rep list)
+  "Returns a new list where the elements in LIST that does not match the PRED function
+are unchanged, and where the elements in LIST that do match the PRED function are mapped
+through the REP function."
+  (--map-when (funcall pred it) (funcall rep it) list))
+
+(defalias '--replace-where '--map-when)
+(defalias '-replace-where '-map-when)
+
+(defun -flatten (l)
+  "Takes a nested list L and returns its contents as a single, flat list."
+  (if (and (listp l) (listp (cdr l)))
+      (-mapcat '-flatten l)
+    (list l)))
+
+(defun -concat (&rest lists)
+  "Returns a new list with the concatenation of the elements in the supplied LISTS."
+  (apply 'append lists))
+
+(defmacro --mapcat (form list)
+  "Anaphoric form of `-mapcat'."
+  `(apply 'append (--map ,form ,list)))
+
+(defun -mapcat (fn list)
+  "Returns the result of applying concat to the result of applying map to FN and LIST.
+Thus function FN should return a collection."
+  (--mapcat (funcall fn it) list))
+
+(defun -cons* (&rest args)
+  "Makes a new list from the elements of ARGS.
+
+The last 2 members of ARGS are used as the final cons of the
+result so if the final member of ARGS is not a list the result is
+a dotted list."
+  (let (res)
+    (--each
+     args
+      (cond
+        ((not res)
+         (setq res it))
+        ((consp res)
+         (setcdr res (cons (cdr res) it)))
+        (t
+         (setq res (cons res it)))))
+    res))
+
+(defmacro --first (form list)
+  "Anaphoric form of `-first'."
+  (let ((n (make-symbol "needle")))
+    `(let (,n)
+       (--each-while ,list (not ,n)
+         (when ,form (setq ,n it)))
+       ,n)))
+
+(defun -first (pred list)
+  "Returns the first x in LIST where (PRED x) is non-nil, else nil.
+
+To get the first item in the list no questions asked, use `car'."
+  (--first (funcall pred it) list))
+
+(defmacro --last (form list)
+  "Anaphoric form of `-last'."
+  (let ((n (make-symbol "needle")))
+    `(let (,n)
+       (--each ,list
+         (when ,form (setq ,n it)))
+       ,n)))
+
+(defun -last (pred list)
+  "Return the last x in LIST where (PRED x) is non-nil, else nil."
+  (--last (funcall pred it) list))
+
+(defmacro --count (pred list)
+  "Anaphoric form of `-count'."
+  (let ((r (make-symbol "result")))
+    `(let ((,r 0))
+       (--each ,list (when ,pred (setq ,r (1+ ,r))))
+       ,r)))
+
+(defun -count (pred list)
+  "Counts the number of items in LIST where (PRED item) is non-nil."
+  (--count (funcall pred it) list))
+
+(defun ---truthy? (val)
+  (not (null val)))
+
+(defmacro --any? (form list)
+  "Anaphoric form of `-any?'."
+  `(---truthy? (--first ,form ,list)))
+
+(defun -any? (pred list)
+  "Returns t if (PRED x) is non-nil for any x in LIST, else nil.
+
+Alias: `-some?'"
+  (--any? (funcall pred it) list))
+
+(defalias '-some? '-any?)
+(defalias '--some? '--any?)
+
+(defalias '-any-p '-any?)
+(defalias '--any-p '--any?)
+(defalias '-some-p '-any?)
+(defalias '--some-p '--any?)
+
+(defmacro --all? (form list)
+  "Anaphoric form of `-all?'."
+  (let ((a (make-symbol "all")))
+    `(let ((,a t))
+       (--each-while ,list ,a (setq ,a ,form))
+       (---truthy? ,a))))
+
+(defun -all? (pred list)
+  "Returns t if (PRED x) is non-nil for all x in LIST, else nil.
+
+Alias: `-every?'"
+  (--all? (funcall pred it) list))
+
+(defalias '-every? '-all?)
+(defalias '--every? '--all?)
+
+(defalias '-all-p '-all?)
+(defalias '--all-p '--all?)
+(defalias '-every-p '-all?)
+(defalias '--every-p '--all?)
+
+(defmacro --none? (form list)
+  "Anaphoric form of `-none?'."
+  `(--all? (not ,form) ,list))
+
+(defun -none? (pred list)
+  "Returns t if (PRED x) is nil for all x in LIST, else nil."
+  (--none? (funcall pred it) list))
+
+(defalias '-none-p '-none?)
+(defalias '--none-p '--none?)
+
+(defmacro --only-some? (form list)
+  "Anaphoric form of `-only-some?'."
+  (let ((y (make-symbol "yes"))
+        (n (make-symbol "no")))
+    `(let (,y ,n)
+       (--each-while ,list (not (and ,y ,n))
+         (if ,form (setq ,y t) (setq ,n t)))
+       (---truthy? (and ,y ,n)))))
+
+(defun -only-some? (pred list)
+  "Returns `t` if there is a mix of items in LIST that matches and does not match PRED.
+Returns `nil` both if all items match the predicate, and if none of the items match the predicate."
+  (--only-some? (funcall pred it) list))
+
+(defalias '-only-some-p '-only-some?)
+(defalias '--only-some-p '--only-some?)
+
+(defun -slice (list from &optional to)
+  "Return copy of LIST, starting from index FROM to index TO.
+FROM or TO may be negative."
+  (let ((length (length list))
+        (new-list nil)
+        (index 0))
+    ;; to defaults to the end of the list
+    (setq to (or to length))
+    ;; handle negative indices
+    (when (< from 0)
+      (setq from (mod from length)))
+    (when (< to 0)
+      (setq to (mod to length)))
+
+    ;; iterate through the list, keeping the elements we want
+    (while (< index to)
+      (when (>= index from)
+        (!cons (car list) new-list))
+      (!cdr list)
+      (setq index (1+ index)))
+    (nreverse new-list)))
+
+(defun -take (n list)
+  "Returns a new list of the first N items in LIST, or all items if there are fewer than N."
+  (let (result)
+    (--dotimes n
+      (when list
+        (!cons (car list) result)
+        (!cdr list)))
+    (nreverse result)))
+
+(defun -drop (n list)
+  "Returns the tail of LIST without the first N items."
+  (--dotimes n (!cdr list))
+  list)
+
+(defmacro --take-while (form list)
+  "Anaphoric form of `-take-while'."
+  (let ((r (make-symbol "result")))
+    `(let (,r)
+       (--each-while ,list ,form (!cons it ,r))
+       (nreverse ,r))))
+
+(defun -take-while (pred list)
+  "Returns a new list of successive items from LIST while (PRED item) returns a non-nil value."
+  (--take-while (funcall pred it) list))
+
+(defmacro --drop-while (form list)
+  "Anaphoric form of `-drop-while'."
+  (let ((l (make-symbol "list")))
+    `(let ((,l ,list))
+       (while (and ,l (let ((it (car ,l))) ,form))
+         (!cdr ,l))
+       ,l)))
+
+(defun -drop-while (pred list)
+  "Returns the tail of LIST starting from the first item for which (PRED item) returns nil."
+  (--drop-while (funcall pred it) list))
+
+(defun -split-at (n list)
+  "Returns a list of ((-take N LIST) (-drop N LIST)), in no more than one pass through the list."
+  (let (result)
+    (--dotimes n
+      (when list
+        (!cons (car list) result)
+        (!cdr list)))
+    (list (nreverse result) list)))
+
+(defmacro --split-with (pred list)
+  "Anaphoric form of `-split-with'."
+  (let ((l (make-symbol "list"))
+        (r (make-symbol "result"))
+        (c (make-symbol "continue")))
+    `(let ((,l ,list)
+           (,r nil)
+           (,c t))
+       (while (and ,l ,c)
+         (let ((it (car ,l)))
+           (if (not ,pred)
+               (setq ,c nil)
+             (!cons it ,r)
+             (!cdr ,l))))
+       (list (nreverse ,r) ,l))))
+
+(defun -split-with (pred list)
+  "Returns a list of ((-take-while PRED LIST) (-drop-while PRED LIST)), in no more than one pass through the list."
+  (--split-with (funcall pred it) list))
+
+(defmacro --separate (form list)
+  "Anaphoric form of `-separate'."
+  (let ((y (make-symbol "yes"))
+        (n (make-symbol "no")))
+    `(let (,y ,n)
+       (--each ,list (if ,form (!cons it ,y) (!cons it ,n)))
+       (list (nreverse ,y) (nreverse ,n)))))
+
+(defun -separate (pred list)
+  "Returns a list of ((-filter PRED LIST) (-remove PRED LIST)), in one pass through the list."
+  (--separate (funcall pred it) list))
+
+(defun -partition (n list)
+  "Returns a new list with the items in LIST grouped into N-sized sublists.
+If there are not enough items to make the last group N-sized,
+those items are discarded."
+  (let ((result nil)
+        (sublist nil)
+        (len 0))
+    (while list
+      (!cons (car list) sublist)
+      (setq len (1+ len))
+      (when (= len n)
+        (!cons (nreverse sublist) result)
+        (setq sublist nil)
+        (setq len 0))
+      (!cdr list))
+    (nreverse result)))
+
+(defun -partition-all (n list)
+  "Returns a new list with the items in LIST grouped into N-sized sublists.
+The last group may contain less than N items."
+  (let (result)
+    (while list
+      (!cons (-take n list) result)
+      (setq list (-drop n list)))
+    (nreverse result)))
+
+(defmacro --partition-by (form list)
+  "Anaphoric form of `-partition-by'."
+  (let ((r (make-symbol "result"))
+        (s (make-symbol "sublist"))
+        (v (make-symbol "value"))
+        (n (make-symbol "new-value"))
+        (l (make-symbol "list")))
+    `(let ((,l ,list))
+       (when ,l
+         (let* ((,r nil)
+                (it (car ,l))
+                (,s (list it))
+                (,v ,form)
+                (,l (cdr ,l)))
+           (while ,l
+             (let* ((it (car ,l))
+                    (,n ,form))
+               (unless (equal ,v ,n)
+                 (!cons (nreverse ,s) ,r)
+                 (setq ,s nil)
+                 (setq ,v ,n))
+               (!cons it ,s)
+               (!cdr ,l)))
+           (!cons (nreverse ,s) ,r)
+           (nreverse ,r))))))
+
+(defun -partition-by (fn list)
+  "Applies FN to each item in LIST, splitting it each time FN returns a new value."
+  (--partition-by (funcall fn it) list))
+
+(defmacro --partition-by-header (form list)
+  "Anaphoric form of `-partition-by-header'."
+  (let ((r (make-symbol "result"))
+        (s (make-symbol "sublist"))
+        (h (make-symbol "header-value"))
+        (b (make-symbol "seen-body?"))
+        (n (make-symbol "new-value"))
+        (l (make-symbol "list")))
+    `(let ((,l ,list))
+       (when ,l
+         (let* ((,r nil)
+                (it (car ,l))
+                (,s (list it))
+                (,h ,form)
+                (,b nil)
+                (,l (cdr ,l)))
+           (while ,l
+             (let* ((it (car ,l))
+                    (,n ,form))
+               (if (equal ,h, n)
+                   (when ,b
+                     (!cons (nreverse ,s) ,r)
+                     (setq ,s nil)
+                     (setq ,b nil))
+                 (setq ,b t))
+               (!cons it ,s)
+               (!cdr ,l)))
+           (!cons (nreverse ,s) ,r)
+           (nreverse ,r))))))
+
+(defun -partition-by-header (fn list)
+  "Applies FN to the first item in LIST. That is the header
+  value. Applies FN to each item in LIST, splitting it each time
+  FN returns the header value, but only after seeing at least one
+  other value (the body)."
+  (--partition-by-header (funcall fn it) list))
+
+(defmacro --group-by (form list)
+  "Anaphoric form of `-group-by'."
+  (let ((l (make-symbol "list"))
+        (v (make-symbol "value"))
+        (k (make-symbol "key"))
+        (r (make-symbol "result")))
+    `(let ((,l ,list)
+           ,r)
+       ;; Convert `list' to an alist and store it in `r'.
+       (while ,l
+         (let* ((,v (car ,l))
+                (it ,v)
+                (,k ,form)
+                (kv (assoc ,k ,r)))
+           (if kv
+               (setcdr kv (cons ,v (cdr kv)))
+             (push (list ,k ,v) ,r))
+           (setq ,l (cdr ,l))))
+       ;; Reverse lists in each group.
+       (let ((rest ,r))
+         (while rest
+           (let ((kv (car rest)))
+             (setcdr kv (nreverse (cdr kv))))
+           (setq rest (cdr rest))))
+       ;; Reverse order of keys.
+       (nreverse ,r))))
+
+(defun -group-by (fn list)
+  "Separate LIST into an alist whose keys are FN applied to the
+elements of LIST.  Keys are compared by `equal'."
+  (--group-by (funcall fn it) list))
+
+(defun -interpose (sep list)
+  "Returns a new list of all elements in LIST separated by SEP."
+  (let (result)
+    (when list
+      (!cons (car list) result)
+      (!cdr list))
+    (while list
+      (setq result (cons (car list) (cons sep result)))
+      (!cdr list))
+    (nreverse result)))
+
+(defun -interleave (&rest lists)
+  "Returns a new list of the first item in each list, then the second etc."
+  (let (result)
+    (while (-none? 'null lists)
+      (--each lists (!cons (car it) result))
+      (setq lists (-map 'cdr lists)))
+    (nreverse result)))
+
+(defmacro --zip-with (form list1 list2)
+  "Anaphoric form of `-zip-with'.
+
+The elements in list1 is bound as `it`, the elements in list2 as `other`."
+  (let ((r (make-symbol "result"))
+        (l1 (make-symbol "list1"))
+        (l2 (make-symbol "list2")))
+    `(let ((,r nil)
+           (,l1 ,list1)
+           (,l2 ,list2))
+       (while (and ,l1 ,l2)
+         (let ((it (car ,l1))
+               (other (car ,l2)))
+           (!cons ,form ,r)
+           (!cdr ,l1)
+           (!cdr ,l2)))
+       (nreverse ,r))))
+
+(defun -zip-with (fn list1 list2)
+  "Zip the two lists LIST1 and LIST2 using a function FN.  This
+function is applied pairwise taking as first argument element of
+LIST1 and as second argument element of LIST2 at corresponding
+position.
+
+The anaphoric form `--zip-with' binds the elements from LIST1 as `it`,
+and the elements from LIST2 as `other`."
+  (--zip-with (funcall fn it other) list1 list2))
+
+(defun -zip (list1 list2)
+  "Zip the two lists together.  Return the list where elements
+are cons pairs with car being element from LIST1 and cdr being
+element from LIST2.  The length of the returned list is the
+length of the shorter one."
+  (-zip-with 'cons list1 list2))
+
+(defun -partial (fn &rest args)
+  "Takes a function FN and fewer than the normal arguments to FN,
+and returns a fn that takes a variable number of additional ARGS.
+When called, the returned function calls FN with ARGS first and
+then additional args."
+  (apply 'apply-partially fn args))
+
+(defun -rpartial (fn &rest args)
+  "Takes a function FN and fewer than the normal arguments to FN,
+and returns a fn that takes a variable number of additional ARGS.
+When called, the returned function calls FN with the additional
+args first and then ARGS.
+
+Requires Emacs 24 or higher."
+  `(closure (t) (&rest args)
+            (apply ',fn (append args ',args))))
+
+(defun -applify (fn)
+  "Changes an n-arity function FN to a 1-arity function that
+expects a list with n items as arguments"
+  (apply-partially 'apply fn))
+
+(defmacro -> (x &optional form &rest more)
+  "Threads the expr through the forms. Inserts X as the second
+item in the first form, making a list of it if it is not a list
+already. If there are more forms, inserts the first form as the
+second item in second form, etc."
+  (cond
+   ((null form) x)
+   ((null more) (if (listp form)
+                    `(,(car form) ,x ,@(cdr form))
+                  (list form x)))
+   (:else `(-> (-> ,x ,form) ,@more))))
+
+(defmacro ->> (x form &rest more)
+  "Threads the expr through the forms. Inserts X as the last item
+in the first form, making a list of it if it is not a list
+already. If there are more forms, inserts the first form as the
+last item in second form, etc."
+  (if (null more)
+      (if (listp form)
+          `(,(car form) ,@(cdr form) ,x)
+        (list form x))
+    `(->> (->> ,x ,form) ,@more)))
+
+(defmacro --> (x form &rest more)
+  "Threads the expr through the forms. Inserts X at the position
+signified by the token `it' in the first form. If there are more
+forms, inserts the first form at the position signified by `it'
+in in second form, etc."
+  (if (null more)
+      (if (listp form)
+          (--map-when (eq it 'it) x form)
+        (list form x))
+    `(--> (--> ,x ,form) ,@more)))
+
+(put '-> 'lisp-indent-function 1)
+(put '->> 'lisp-indent-function 1)
+(put '--> 'lisp-indent-function 1)
+
+(defun -distinct (list)
+  "Return a new list with all duplicates removed.
+The test for equality is done with `equal',
+or with `-compare-fn' if that's non-nil.
+
+Alias: `-uniq'"
+  (let (result)
+    (--each list (unless (-contains? result it) (!cons it result)))
+    (nreverse result)))
+
+(defun -union (list list2)
+  "Return a new list containing the elements of LIST1 and elements of LIST2 that are not in LIST1.
+The test for equality is done with `equal',
+or with `-compare-fn' if that's non-nil."
+  (let (result)
+    (--each list (!cons it result))
+    (--each list2 (unless (-contains? result it) (!cons it result)))
+    (nreverse result)))
+
+(defalias '-uniq '-distinct)
+
+(defun -intersection (list list2)
+  "Return a new list containing only the elements that are members of both LIST and LIST2.
+The test for equality is done with `equal',
+or with `-compare-fn' if that's non-nil."
+  (--filter (-contains? list2 it) list))
+
+(defun -difference (list list2)
+  "Return a new list with only the members of LIST that are not in LIST2.
+The test for equality is done with `equal',
+or with `-compare-fn' if that's non-nil."
+  (--filter (not (-contains? list2 it)) list))
+
+(defvar -compare-fn nil
+  "Tests for equality use this function or `equal' if this is nil.
+It should only be set using dynamic scope with a let, like:
+(let ((-compare-fn =)) (-union numbers1 numbers2 numbers3)")
+
+(defun -contains? (list element)
+  "Return whether LIST contains ELEMENT.
+The test for equality is done with `equal',
+or with `-compare-fn' if that's non-nil."
+  (not
+   (null
+    (cond
+     ((null -compare-fn)    (member element list))
+     ((eq -compare-fn 'eq)  (memq element list))
+     ((eq -compare-fn 'eql) (memql element list))
+     (t
+      (let ((lst list))
+        (while (and lst
+                    (not (funcall -compare-fn element (car lst))))
+          (setq lst (cdr lst)))
+        lst))))))
+
+(defalias '-contains-p '-contains?)
+
+(defun -repeat (n x)
+  "Return a list with X repeated N times.
+Returns nil if N is less than 1."
+  (let (ret)
+    (--dotimes n (!cons x ret))
+    ret))
+
+(eval-after-load "lisp-mode"
+  '(progn
+     (let ((new-keywords '(
+                           "--each"
+                           "-each"
+                           "--each-while"
+                           "-each-while"
+                           "--dotimes"
+                           "-dotimes"
+                           "-map"
+                           "--map"
+                           "--reduce-from"
+                           "-reduce-from"
+                           "--reduce"
+                           "-reduce"
+                           "--filter"
+                           "-filter"
+                           "-select"
+                           "--select"
+                           "--remove"
+                           "-remove"
+                           "-reject"
+                           "--reject"
+                           "--keep"
+                           "-keep"
+                           "-flatten"
+                           "-concat"
+                           "--mapcat"
+                           "-mapcat"
+                           "--first"
+                           "-first"
+                           "--any?"
+                           "-any?"
+                           "-some?"
+                           "--some?"
+                           "-any-p"
+                           "--any-p"
+                           "-some-p"
+                           "--some-p"
+                           "--all?"
+                           "-all?"
+                           "-every?"
+                           "--every?"
+                           "-all-p"
+                           "--all-p"
+                           "-every-p"
+                           "--every-p"
+                           "--none?"
+                           "-none?"
+                           "-none-p"
+                           "--none-p"
+                           "-only-some?"
+                           "--only-some?"
+                           "-only-some-p"
+                           "--only-some-p"
+                           "-take"
+                           "-drop"
+                           "--take-while"
+                           "-take-while"
+                           "--drop-while"
+                           "-drop-while"
+                           "-split-at"
+                           "--split-with"
+                           "-split-with"
+                           "-partition"
+                           "-partition-all"
+                           "-interpose"
+                           "-interleave"
+                           "--zip-with"
+                           "-zip-with"
+                           "-zip"
+                           "--map-when"
+                           "-map-when"
+                           "--replace-where"
+                           "-replace-where"
+                           "-partial"
+                           "-rpartial"
+                           "->"
+                           "->>"
+                           "-->"
+                           "-distinct"
+                           "-intersection"
+                           "-difference"
+                           "-contains?"
+                           "-contains-p"
+                           "-repeat"
+                           "-cons*"
+                           ))
+           (special-variables '(
+                                "it"
+                                "it-index"
+                                "acc"
+                                "other"
+                                )))
+       (font-lock-add-keywords 'emacs-lisp-mode `((,(concat "\\<" (regexp-opt special-variables 'paren) "\\>")
+                                                   1 font-lock-variable-name-face)) 'append)
+       (font-lock-add-keywords 'emacs-lisp-mode `((,(concat "(\\s-*" (regexp-opt new-keywords 'paren) "\\>")
+                                                   1 font-lock-keyword-face)) 'append))
+     (--each (buffer-list)
+       (with-current-buffer it
+         (when (and (eq major-mode 'emacs-lisp-mode)
+                    (boundp 'font-lock-mode)
+                    font-lock-mode)
+           (font-lock-refresh-defaults))))))
+
+(provide 'dash)
+;;; dash.el ends here
index 51bc718..09e208e 100644 (file)
@@ -839,6 +839,24 @@ so change the default 'F' binding in the agenda to allow both"
         (revert-buffer t t t) )))
   (message "Refreshed open files.") )
 
+;;;###autoload
+(defun move-line-up ()
+  "Move up the current line."
+  (interactive)
+  (transpose-lines 1)
+  (forward-line -2)
+  (indent-according-to-mode))
+
+;;;###autoload
+(defun move-line-down ()
+  "Move down the current line."
+  (interactive)
+  (forward-line 1)
+  (transpose-lines 1)
+  (forward-line -1)
+  (indent-according-to-mode))
+
+
 (provide 'ganneff)
 
 ;(setq org-icalendar-verify-function 'org-mycal-export-limit)
diff --git a/elisp/sunrise/.dir-locals.el b/elisp/sunrise/.dir-locals.el
new file mode 100644 (file)
index 0000000..e063b87
--- /dev/null
@@ -0,0 +1,2 @@
+((nil . ((fill-column . 80)
+         (default-justification . left))))
diff --git a/elisp/sunrise/README.md b/elisp/sunrise/README.md
new file mode 100644 (file)
index 0000000..d98c72c
--- /dev/null
@@ -0,0 +1,11 @@
+Welcome to the Sunrise Commander Git repository. This is the place where you can
+contribute directly to the development of the Sunrise Commander File Manager for
+GNU Emacs.
+
+If you don't know what the Sunrise Commander is, or just want to grab a copy to
+use in your own Emacs please read [this Emacs Wiki page](http://www.emacswiki.org/emacs/Sunrise_Commander).
+
+If you wish to contribute, please fork the project, commit your work to your own
+branch and send me a pull request.
+
+Happy hacking!
diff --git a/elisp/sunrise/sunrise-commander.el b/elisp/sunrise/sunrise-commander.el
new file mode 100644 (file)
index 0000000..5cba59d
--- /dev/null
@@ -0,0 +1,4291 @@
+;;; sunrise-commander.el --- two-pane file manager for Emacs based on Dired and inspired by MC  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2007-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 24 Sep 2007
+;; Version: 6
+;; RCS Version: $Rev: 446 $
+;; Keywords: files, dired, midnight commander, norton, orthodox
+;; URL: http://www.emacswiki.org/emacs/sunrise-commander.el
+;; Compatibility: GNU Emacs 22+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; The Sunrise Commmander is an double-pane file manager for Emacs. It's built
+;; atop of Dired and takes advantage of all its power, but also provides many
+;; handy features of its own:
+
+;; * Sunrise is implemented as a derived major mode confined inside the pane
+;; buffers, so its buffers and Dired ones can live together without easymenu or
+;; viper to avoid key binding collisions.
+
+;; * It automatically closes unused buffers and tries to never keep open more
+;; than the one or two used to display the panes, though this behavior may be
+;; disabled if desired.
+
+;; * Each pane has its own history stack: press M-y / M-u for moving backwards /
+;; forwards in the history of directories.
+
+;; * Press M-t to swap (transpose) the panes.
+
+;; * Press C-= for "smart" file comparison using `ediff'. It compares together
+;; the first two files marked on each pane or, if no files have been marked, it
+;; assumes that the second pane contains a file with the same name as the
+;; selected one and tries to compare these two. You can also mark whole lists of
+;; files to be compared and then just press C-= for comparing the next pair.
+
+;; * Press = for fast "smart" file comparison -- like above, but using regular
+;; diff.
+
+;; * Press C-M-= for directory comparison (by date / size / contents of files).
+
+;; * Press C-c C-s to change the layout of the panes (horizontal/vertical/top)
+
+;; * Press C-c / to interactively refine the contents of the current pane using
+;; fuzzy (a.k.a. flex) matching, then:
+;;    - press Delete or Backspace to revert the buffer to its previous state
+;;    - press Return, C-n or C-p to exit and accept the current narrowed state
+;;    - press Esc or C-g to abort the operation and revert the buffer
+;;    - use ! to prefix characters that should NOT appear after a given position
+;; Once narrowed and accepted, you can restore the original contents of the pane
+;; by pressing g (revert-buffer).
+
+;; * Sticky search: press C-c s to launch an interactive search that will remain
+;; active from directory to directory, until you hit a regular file or press C-g
+
+;; * Press C-x C-q to put the current pane in Editable Dired mode (allows to
+;; edit the pane as if it were a regular file -- press C-c C-c to commit your
+;; changes to the filesystem, or C-c C-k to abort).
+
+;; * Press y to recursively calculate the total size (in bytes) of all files and
+;; directories currently selected/marked in the active pane.
+
+;; * Sunrise VIRTUAL mode integrates dired-virtual mode to Sunrise, allowing to
+;; capture find and locate results in regular files and to use them later as if
+;; they were directories with all Dired and Sunrise operations at your
+;; fingertips.
+;; The results of the following operations are displayed in VIRTUAL mode:
+;;    - find-name-dired (press C-c C-n),
+;;    - find-grep-dired (press C-c C-g),
+;;    - find-dired      (press C-c C-f),
+;;    - locate          (press C-c C-l),
+;;    - list all recently visited files (press C-c C-r -- requires recentf),
+;;    - list all directories in active pane's history ring (press C-c C-d).
+
+;; * Supports AVFS (http://avf.sourceforge.net/) for transparent navigation
+;; inside compressed archives (*.zip, *.tgz, *.tar.bz2, *.deb, etc. etc.)
+;; You need to have AVFS with coda or fuse installed and running on your system
+;; for this to work, though.
+
+;; * Opening terminals directly from Sunrise:
+;;    - Press C-c C-t to inconditionally open a new terminal into the currently
+;;      selected directory in the active pane.
+;;    - Use C-c t to switch to the last opened terminal, or (when already inside
+;;      a terminal) to cycle through all open terminals.
+;;    - Press C-c T to switch to the last opened terminal and change directory
+;;      to the one in the current directory.
+;;    - Press C-c M-t to be prompted for a program name, and then open a new
+;;      terminal using that program into the currently selected directory
+;;      (eshell is a valid value; if no program can be found with the given name
+;;      then the value of `sr-terminal-program' is used instead).
+
+;; * Terminal integration and Command line expansion: integrates tightly with
+;; `eshell' and `term-mode' to allow interaction between terminal emulators in
+;; line mode (C-c C-j) and the panes: the most important navigation commands
+;; (up, down, mark, unmark, go to parent dir) can be executed on the active pane
+;; directly from the terminal by pressing the usual keys with Meta: <M-up>,
+;; <M-down>, etc. Additionally, the following substitutions are automagically
+;; performed in `eshell' and `term-line-mode':
+;;     %f - expands to the currently selected file in the left pane
+;;     %F - expands to the currently selected file in the right pane
+;;     %m - expands to the list of paths of all marked files in the left pane
+;;     %M - expands to the list of paths of all marked files in the right pane
+;;     %n - expands to the list of names of all marked files in the left pane
+;;     %N - expands to the list of names of all marked files in the right pane
+;;     %d - expands to the current directory in the left pane
+;;     %D - expands to the current directory in the right pane
+;;     %a - expands to the list of paths of all marked files in the active pane
+;;     %A - expands to the current directory in the active pane
+;;     %p - expands to the list of paths of all marked files in the passive pane
+;;     %P - expands to the current directory in the passive pane
+
+;; * Cloning of complete directory trees: press K to clone the selected files
+;; and directories into the passive pane. Cloning is a more general operation
+;; than copying, in which all directories are recursively created with the same
+;; names and structures at the destination, while what happens to the files
+;; within them depends on the option you choose:
+;;    - "(D)irectories only" ignores all files, copies only directories,
+;;    - "(C)opies" performs a regular recursive copy of all files and dirs,
+;;    - "(H)ardlinks" makes every new file a (hard) link to the original one
+;;    - "(S)ymlinks" creates absolute symbolic links for all files in the tree,
+;;    - "(R)elative symlinks” creates relative symbolic links.
+
+;; * Passive navigation: the usual navigation keys (n, p, Return, U, ;) combined
+;; with Meta allow to move across the passive pane without actually having to
+;; switch to it.
+
+;; * Synchronized navigation: press C-c C-z to enable / disable synchronized
+;; navigation. In this mode, the passive navigation keys (M-n, M-p, M-Return,
+;; etc.) operate on both panes simultaneously. I've found this quite useful for
+;; comparing hierarchically small to medium-sized directory trees (for large to
+;; very large directory trees one needs something on the lines of diff -r
+;; though).
+
+;; * And much more -- press ? while in Sunrise mode for basic help, or h for a
+;; complete list of all keybindings available (use C-e and C-y to scroll).
+
+;; There is no help window like in MC, but if you really miss it, just get and
+;; install the sunrise-x-buttons extension.
+
+;; A lot of this code was once adapted from Kevin Burton's mc.el, but it has
+;; evolved considerably since then. Another part (the code for file copying and
+;; renaming) derives originally from the Dired extensions written by Kurt
+;; Nørmark for LAML (http://www.cs.aau.dk/~normark/scheme/distribution/laml/).
+
+;; It was written on GNU Emacs 24 on Linux and tested on GNU Emacs 22, 23 and 24
+;; for Linux and on EmacsW32 (version 23) for Windows. I have also received
+;; feedback from users reporting it works OK on the Mac. It does not work either
+;; on GNU Emacs 21 or XEmacs -- please drop me a line if you would like to help
+;; porting it. All contributions and/or bug reports will be very welcome.
+
+;; For more details on the file manager, several available extensions and many
+;; cool tips & tricks visit http://www.emacswiki.org/emacs/Sunrise_Commander
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'.
+
+;; 2) Add a (require 'sunrise-commander) to your .emacs file.
+
+;; 3) Choose some unused extension for files to be opened in Sunrise VIRTUAL
+;; mode and add it to `auto-mode-alist', e.g. if you want to name your virtual
+;; directories like *.svrm just add to your .emacs file a line like the
+;; following:
+;;
+;;     (add-to-list 'auto-mode-alist '("\\.srvm\\'" . sr-virtual-mode))
+
+;; 4) Evaluate the new lines, or reload your .emacs file, or restart Emacs.
+
+;; 5) Type M-x sunrise to invoke the Sunrise Commander (or much better: bind the
+;; function to your favorite key combination). The command `sunrise-cd' invokes
+;; Sunrise and automatically selects the current file wherever it is in the
+;; filesystem. Type h at any moment for information on available key bindings.
+
+;; 6) Type M-x customize-group <RET> sunrise <RET> to customize options, fonts
+;; and colors (activate AVFS support here, too).
+
+;; 7) Enjoy :)
+
+;;; Code:
+
+(require 'advice)
+(require 'desktop)
+(require 'dired)
+(require 'dired-aux)
+(require 'dired-x)
+(require 'enriched)
+(require 'esh-mode)
+(require 'find-dired)
+(require 'font-lock)
+(require 'hl-line)
+(require 'sort)
+(require 'term)
+(eval-when-compile (require 'cl)
+                   (require 'recentf)
+                   (require 'tramp))
+
+(eval-and-compile
+  (unless (fboundp 'cl-labels)
+    (defalias 'cl-labels 'labels))
+  (unless (fboundp 'cl-letf)
+    (defalias 'cl-letf 'letf)))
+
+(defgroup sunrise nil
+  "The Sunrise Commander File Manager."
+  :group 'files)
+
+(defcustom sr-show-file-attributes t
+  "Whether to initially display file attributes in Sunrise panes.
+You can always toggle file attributes display pressing
+\\<sr-mode-map>\\[sr-toggle-attributes]."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-autoload-extensions t
+  "Whether to load extensions immediately after their declaration, or when the
+SC core is loaded (e.g. when using autoload cookies)."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-show-hidden-files nil
+  "Whether to initially display hidden files in Sunrise panes.
+You can always toggle hidden files display pressing
+\\<sr-mode-map>\\[dired-omit-mode].
+You can also customize what files are considered hidden by setting
+`dired-omit-files' and `dired-omit-extensions' in your .emacs file."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-terminal-kill-buffer-on-exit t
+  "Whether to kill terminal buffers after their shell process ends."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-terminal-program "eshell"
+  "The program to use for terminal emulation.
+If this value is set to \"eshell\", the Emacs shell (`eshell')
+will be used."
+  :group 'sunrise
+  :type 'string)
+
+(defcustom sr-listing-switches "-al"
+  "Listing switches passed to `ls' when building Sunrise buffers.
+\(Cf. `dired-listing-switches'.)
+  Most portable value: -al
+  Recommended value on GNU systems: \
+--time-style=locale --group-directories-first -alDhgG"
+  :group 'sunrise
+  :type 'string)
+
+(defcustom sr-virtual-listing-switches "-ald"
+  "Listing switches for building buffers in `sr-virtual-mode'.
+Should not contain the -D option. See also `sr-listing-switches'."
+  :group 'sunrise
+  :type 'string)
+
+(defcustom sr-avfs-root nil
+  "Root of the AVFS virtual filesystem used for navigating compressed archives.
+Setting this value activates AVFS support."
+  :group 'sunrise
+  :type '(choice
+          (const :tag "AVFS support disabled" nil)
+          (directory :tag "AVFS root directory")))
+
+(defcustom sr-avfs-handlers-alist '(("\\.[jwesh]ar$" . "#uzip/")
+                                    ("\\.wsar$"      . "#uzip/")
+                                    ("\\.xpi$"       . "#uzip/")
+                                    ("\\.apk$"       . "#uzip/")
+                                    ("\\.iso$"       . "#iso9660/")
+                                    ("\\.patch$"     . "#/")
+                                    ("\\.txz$"       . "#/")
+                                    ("."             . "#/"))
+  "List of AVFS handlers to manage specific file extensions."
+  :group 'sunrise
+  :type 'alist)
+
+(defcustom sr-md5-shell-command "md5sum %f | cut -d' ' -f1 2>/dev/null"
+  "Shell command to use for calculating MD5 sums for files.
+Used when comparing directories using the ``(c)ontents'' option.
+Use %f as a placeholder for the name of the file."
+  :group 'sunrise
+  :type 'string)
+
+(defcustom sr-window-split-style 'horizontal
+  "The current window split configuration.
+May be `horizontal', `vertical' or `top'."
+  :group 'sunrise
+  :type '(choice
+          (const horizontal)
+          (const vertical)
+          (const top)))
+
+(defcustom sr-windows-locked t
+  "When non-nil, vertical size of the panes will remain constant."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-windows-default-ratio 66
+  "Percentage of the total height of the frame to use by default for the Sunrise
+Commander panes."
+  :group 'sunrise
+  :type 'integer
+  :set (defun sr-set-windows-default-ratio (symbol value)
+         "Setter function for the `sr-windows-default-ratio' custom option."
+         (if (and (integerp value) (>= value 0) (<= value 100))
+             (set-default symbol value)
+           (error "Invalid value: %s" value))))
+
+(defcustom sr-history-length 20
+  "Number of entries to keep in each pane's history rings."
+  :group 'sunrise
+  :type 'integer)
+
+(defcustom sr-kill-unused-buffers t
+  "Whether buffers should be killed automatically by Sunrise when not displayed
+in any of the panes."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-confirm-kill-viewer t
+  "Whether to ask for confirmation before killing a buffer opened in quick-view
+mode."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-attributes-display-mask nil
+  "Contols hiding/transforming columns with `sr-toggle-attributes'.
+If set, its value must be a list of symbols, one for each
+attributes column. If the symbol is nil, then the corresponding
+column will be hidden, and if it's not nil then the column will
+be left untouched. The symbol may also be the name of a function
+that takes one string argument and evaluates to a different
+string -- in this case this function will be used to transform
+the contents of the corresponding column and its result will be
+displayed instead."
+  :group 'sunrise
+  :type '(repeat symbol))
+
+(defcustom sr-fast-backup-extension ".bak"
+  "Determines the extension to append to the names of new files
+created with the `sr-fast-backup-files' function (@!). This can
+be either a simple string or an s-expression to be evaluated at
+run-time."
+  :group 'sunrise
+  :type '(choice
+          (string :tag "Literal text")
+          (sexp :tag "Symbolic expression")))
+
+(defcustom sr-traditional-other-window nil
+  "Sunrise modifies the behavior of the `other-window' command,
+so that focus is always given to the currently selected pane when
+switching from external windows. If you'd prefer the original
+Emacs behavior instead, then set this flag to t."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-fuzzy-negation-character ?!
+  "Character to use for negating patterns when fuzzy-narrowing a pane."
+  :group 'sunrise
+  :type '(choice
+          (const :tag "Fuzzy matching negation disabled" nil)
+          (character :tag "Fuzzy matching negation character" ?!)))
+
+(defcustom sr-init-hook nil
+  "List of functions to be called before the Sunrise panes are displayed."
+  :group 'sunrise
+  :type 'hook
+  :options '(auto-insert))
+
+(defcustom sr-start-hook nil
+  "List of functions to be called after the Sunrise panes are displayed."
+  :group 'sunrise
+  :type 'hook
+  :options '(auto-insert))
+
+(defcustom sr-refresh-hook nil
+  "List of functions to be called every time a pane is refreshed."
+  :group 'sunrise
+  :type 'hook
+  :options '(auto-insert))
+
+(defcustom sr-quit-hook nil
+  "List of functions to be called after the Sunrise panes are hidden."
+  :group 'sunrise
+  :type 'hook
+  :options '(auto-insert))
+
+(defvar sr-restore-buffer nil
+  "Buffer to restore when Sunrise quits.")
+
+(defvar sr-prior-window-configuration nil
+  "Window configuration before Sunrise was started.")
+
+(defvar sr-running nil
+  "True when Sunrise commander mode is running.")
+
+(defvar sr-synchronized nil
+  "True when synchronized navigation is on")
+
+(defvar sr-current-window-overlay nil
+  "Holds the current overlay which marks the current Dired buffer.")
+
+(defvar sr-clex-hotchar-overlay nil
+  "Overlay used to highlight the hot character (%) during CLEX operations.")
+
+(defvar sr-left-directory "~/"
+  "Dired directory for the left window. See variable `dired-directory'.")
+
+(defvar sr-left-buffer nil
+  "Dired buffer for the left window.")
+
+(defvar sr-left-window nil
+  "The left window of Dired.")
+
+(defvar sr-right-directory "~/"
+  "Dired directory for the right window. See variable `dired-directory'.")
+
+(defvar sr-right-buffer nil
+  "Dired buffer for the right window.")
+
+(defvar sr-right-window nil
+  "The right window of Dired.")
+
+(defvar sr-current-frame nil
+  "The frame Sunrise is active on (if any).")
+
+(defvar sr-this-directory "~/"
+  "Dired directory in the active pane.
+This isn't necessarily the same as `dired-directory'.")
+
+(defvar sr-other-directory "~/"
+  "Dired directory in the passive pane.")
+
+(defvar sr-selected-window 'left
+  "The window to select when Sunrise starts up.")
+
+(defvar sr-selected-window-width nil
+  "The width the selected window should have on startup.")
+
+(defvar sr-history-registry '((left) (right))
+  "Registry of visited directories for both panes.")
+
+(defvar sr-history-stack '((left 0 . 0) (right 0 . 0))
+  "History stack counters.
+The first counter on each side tracks (by value) the absolute
+depth of the stack and (by sign) the direction it is currently
+being traversed. The second counter points at the position of the
+element that is immediately beneath the top of the stack.")
+
+(defvar sr-ti-openterms nil
+  "Stack of currently open terminal buffers.")
+
+(defvar sr-ediff-on nil
+  "Flag that indicates whether an `ediff' is being currently done.")
+
+(defvar sr-clex-on nil
+  "Flag that indicates that a CLEX operation is taking place.")
+
+(defvar sr-virtual-buffer nil
+  "Local flag that indicates the current buffer was originally in
+  VIRTUAL mode.")
+
+(defvar sr-dired-directory ""
+  "Directory inside which `sr-mode' is currently active.")
+
+(defvar sr-start-message
+  "Been coding all night? Enjoy the Sunrise! (or press q to quit)"
+  "Message to display when Sunrise is started.")
+
+(defvar sr-panes-height nil
+  "Current height of the pane windows.
+Initial value is 2/3 the viewport height.")
+
+(defvar sr-current-path-faces nil
+  "List of faces to display the path in the current pane (first wins)")
+(make-variable-buffer-local 'sr-current-path-faces)
+
+(defvar sr-inhibit-highlight nil
+  "Special variable used to temporarily inhibit highlighting in panes.")
+
+(defvar sr-find-items nil
+  "Special variable used by `sr-find' to control the scope of find operations.")
+
+(defvar sr-desktop-save-handlers nil
+  "List of extension-defined handlers to save Sunrise buffers with desktop.")
+
+(defvar sr-desktop-restore-handlers nil
+  "List of extension-defined handlers to restore Sunrise buffers from desktop.")
+
+(defvar sr-backup-buffer nil
+  "Variable holding a buffer-local value of the backup buffer.")
+(make-variable-buffer-local 'sr-backup-buffer)
+
+(defvar sr-goto-dir-function nil
+  "Function to use to navigate to a given directory, or nil to do
+the default.  The function receives one argument DIR, which is
+the directory to go to.")
+
+(defconst sr-side-lookup (list '(left . right) '(right . left))
+  "Trivial alist used by the Sunrise Commander to lookup its own passive side.")
+
+(defface sr-active-path-face
+  '((((type tty) (class color) (min-colors 8))
+     :background "green" :foreground "yellow" :bold t)
+    (((type tty) (class mono)) :inverse-video t)
+    (t :background "#ace6ac" :foreground "yellow" :bold t :height 120))
+  "Face of the directory path in the active pane."
+  :group 'sunrise)
+
+(defface sr-passive-path-face
+  '((((type tty) (class color) (min-colors 8) (background dark))
+     :background "black" :foreground "cyan")
+    (((type tty) (class color) (min-colors 8) (background light))
+     :background "white" :foreground "cyan")
+    (t :background "white" :foreground "lightgray" :bold t :height 120))
+  "Face of the directory path in the passive pane."
+  :group 'sunrise)
+
+(defface sr-editing-path-face
+  '((t :background "red" :foreground "yellow" :bold t :height 120))
+  "Face of the directory path in the active pane while in editable pane mode."
+  :group 'sunrise)
+
+(defface sr-highlight-path-face
+  '((t :background "yellow" :foreground "#ace6ac" :bold t :height 120))
+  "Face of the directory path on mouse hover."
+  :group 'sunrise)
+
+(defface sr-clex-hotchar-face
+  '((t :foreground "red" :bold t))
+  "Face of the hot character (%) in CLEX mode.
+Indicates that a CLEX substitution may be about to happen."
+  :group 'sunrise)
+
+;;; ============================================================================
+;;; This is the core of Sunrise: the main idea is to apply `sr-mode' only inside
+;;; Sunrise buffers while keeping all of `dired-mode' untouched.
+
+;;; preserve this variable when switching from `dired-mode' to another mode
+(put 'dired-subdir-alist 'permanent-local t)
+
+;;;###autoload
+(define-derived-mode sr-mode dired-mode "Sunrise Commander"
+  "Two-pane file manager for Emacs based on Dired and inspired by MC.
+The following keybindings are available:
+
+        /, j .......... go to directory
+        p, n .......... move cursor up/down
+        M-p, M-n ...... move cursor up/down in passive pane
+        ^, J .......... go to parent directory
+        M-^, M-J ...... go to parent directory in passive pane
+        Tab ........... switch to other pane
+        C-Tab.......... switch to viewer window
+        C-c Tab ....... switch to viewer window (console compatible)
+        RET, f ........ visit selected file/directory
+        M-RET, M-f .... visit selected file/directory in passive pane
+        C-c RET ....... visit selected in passive pane (console compatible)
+        b ............. visit selected file/directory in default browser
+        F ............. visit all marked files, each in its own window
+        C-u F ......... visit all marked files in the background
+        o,v ........... quick visit selected file (scroll with C-M-v, C-M-S-v)
+        C-u o, C-u v .. kill quick-visited buffer (restores normal scrolling)
+        X ............. execute selected file
+        C-u X.......... execute selected file with arguments
+
+        + ............. create new directory
+        M-+ ........... create new empty file(s)
+        C ............. copy marked (or current) files and directories
+        R ............. rename marked (or current) files and directories
+        D ............. delete marked (or current) files and directories
+        S ............. soft-link selected file/directory to passive pane
+        Y ............. do relative soft-link of selected file in passive pane
+        H ............. hard-link selected file to passive pane
+        K ............. clone selected files and directories into passive pane
+        M-C ........... copy (using traditional dired-do-copy)
+        M-R ........... rename (using traditional dired-do-rename)
+        M-D ........... delete (using traditional dired-do-delete)
+        M-S............ soft-link (using traditional dired-do-symlink)
+        M-Y............ do relative soft-link (traditional dired-do-relsymlink)
+        M-H............ hard-link selected file/directory (dired-do-hardlink)
+        A ............. search marked files for regular expression
+        Q ............. perform query-replace-regexp on marked files
+        C-c s ......... start a \"sticky\" interactive search in the current pane
+
+        M-a ........... move to beginning of current directory
+        M-e ........... move to end of current directory
+        M-y ........... go to previous directory in history
+        M-u ........... go to next directory in history
+        C-M-y ......... go to previous directory in history on passive pane
+        C-M-u ......... go to next directory in history on passive pane
+
+        g, C-c C-c .... refresh pane
+        s ............. sort entries (by name, number, size, time or extension)
+        r ............. reverse the order of entries in the active pane (sticky)
+        C-o ........... show/hide hidden files (requires dired-omit-mode)
+        C-Backspace ... hide/show file attributes in pane
+        C-c Backspace . hide/show file attributes in pane (console compatible)
+        y ............. show file type / size of selected files and directories.
+        M-l ........... truncate/continue long lines in pane
+        C-c v ......... put current panel in VIRTUAL mode
+        C-c C-v ....... create new pure VIRTUAL buffer
+        C-c C-w ....... browse directory tree using w3m
+
+        M-t ........... transpose panes
+        M-o ........... synchronize panes
+        C-c C-s ....... change panes layout (vertical/horizontal/top-only)
+        [ ............. enlarges the right pane by 5 columns
+        ] ............. enlarges the left pane by 5 columns
+        } ............. enlarges the panes vertically by 1 row
+        C-} ........... enlarges the panes vertically as much as it can
+        C-c } ......... enlarges the panes vertically as much as it can
+        { ............. shrinks the panes vertically by 1 row
+        C-{ ........... shrinks the panes vertically as much as it can
+        C-c { ......... shrinks the panes vertically as much as it can
+        \\ ............. restores the size of all windows back to «normal»
+        C-c C-z ....... enable/disable synchronized navigation
+
+        C-= ........... smart compare files (ediff)
+        C-c = ......... smart compare files (console compatible)
+        = ............. fast smart compare files (plain diff)
+        C-M-= ......... compare panes
+        C-x = ......... compare panes (console compatible)
+
+        C-c C-f ....... execute Find-dired in Sunrise VIRTUAL mode
+        C-c C-n ....... execute find-Name-dired in Sunrise VIRTUAL mode
+        C-c C-g ....... execute find-Grep-dired in Sunrise VIRTUAL mode
+        C-u C-c C-g ... execute find-Grep-dired with additional grep options
+        C-c C-l ....... execute Locate in Sunrise VIRTUAL mode
+        C-c C-r ....... browse list of Recently visited files (requires recentf)
+        C-c C-c ....... [after find, locate or recent] dismiss virtual buffer
+        C-c / ......... narrow the contents of current pane using fuzzy matching
+        C-c b ......... partial Branch view of selected items in current pane
+        C-c p ......... Prune paths matching regular expression from current pane
+        ; ............. follow file (go to same directory as selected file)
+        M-; ........... follow file in passive pane
+        C-M-o ......... follow a projection of current directory in passive pane
+
+        C-> ........... save named checkpoint (a.k.a. \"bookmark panes\")
+        C-c > ......... save named checkpoint (console compatible)
+        C-.    ........ restore named checkpoint
+        C-c .  ........ restore named checkpoint
+
+        C-x C-q ....... put pane in Editable Dired mode (commit with C-c C-c)
+        @! ............ fast backup files (not dirs!), each to [filename].bak
+
+        C-c t ......... open new terminal or switch to already open one
+        C-c T ......... open terminal AND/OR change directory to current
+        C-c C-t ....... open always a new terminal in current directory
+        C-c M-t ....... open a new terminal using an alternative shell program
+        q, C-x k ...... quit Sunrise Commander, restore previous window setup
+        M-q ........... quit Sunrise Commander, don't restore previous windows
+
+Additionally, the following traditional commander-style keybindings are provided
+\(these may be disabled by customizing the `sr-use-commander-keys' option):
+
+        F2 ............ go to directory
+        F3 ............ quick visit selected file
+        F4 ............ visit selected file
+        F5 ............ copy marked (or current) files and directories
+        F6 ............ rename marked (or current) files and directories
+        F7 ............ create new directory
+        F8 ............ delete marked (or current) files and directories
+        F10 ........... quit Sunrise Commander
+        C-F3 .......... sort contents of current pane by name
+        C-F4 .......... sort contents of current pane by extension
+        C-F5 .......... sort contents of current pane by time
+        C-F6 .......... sort contents of current pane by size
+        C-F7 .......... sort contents of current pane numerically
+        S-F7 .......... soft-link selected file/directory to passive pane
+        Insert ........ mark file
+        C-PgUp ........ go to parent directory
+
+Any other dired keybinding (not overridden by any of the above) can be used in
+Sunrise, like G for changing group, M for changing mode and so on.
+
+Some more bindings are available in terminals opened using any of the Sunrise
+functions (i.e. one of: C-c t, C-c T, C-c C-t, C-c M-t):
+
+        C-c Tab ....... switch focus to the active pane
+        C-c t ......... cycle through all currently open terminals
+        C-c T ......... cd to the directory in the active pane
+        C-c C-t ....... open new terminal, cd to directory in the active pane
+        C-c ; ......... follow the current directory in the active pane
+        C-c { ......... shrink the panes vertically as much as possible
+        C-c } ......... enlarge the panes vertically as much as possible
+        C-c \\ ......... restore the size of all windows back to «normal»
+        C-c C-j ....... put terminal in line mode
+        C-c C-k ....... put terminal back in char mode
+
+The following bindings are available only in line mode (eshell is considered to
+be *always* in line mode):
+
+        M-<up>, M-P ... move cursor up in the active pane
+        M-<down>, M-N . move cursor down in the active pane
+        M-Return ...... visit selected file/directory in the active pane
+        M-J ........... go to parent directory in the active pane
+        M-G ........... refresh active pane
+        M-Tab ......... switch to passive pane (without leaving the terminal)
+        M-M ........... mark selected file/directory in the active pane
+        M-Backspace ... unmark previous file/directory in the active pane
+        M-U ........... remove all marks from the active pane
+        C-Tab ......... switch focus to the active pane
+
+In a terminal in line mode the following substitutions are also performed
+automatically:
+
+       %f - expands to the currently selected file in the left pane
+       %F - expands to the currently selected file in the right pane
+       %m - expands to the list of paths of all marked files in the left pane
+       %M - expands to the list of paths of all marked files in the right pane
+       %n - expands to the list of names of all marked files in the left pane
+       %N - expands to the list of names of all marked files in the right pane
+       %d - expands to the current directory in the left pane
+       %D - expands to the current directory in the right pane
+       %a - expands to the list of paths of all marked files in the active pane
+       %A - expands to the current directory in the active pane
+       %p - expands to the list of paths of all marked files in the passive pane
+       %P - expands to the current directory in the passive pane
+       %% - inserts a single % sign.
+"
+  :group 'sunrise
+  (unless (string-match "\\(Sunrise\\)" (buffer-name))
+    (rename-buffer (concat (buffer-name) " (Sunrise)") t))
+  (set-keymap-parent sr-mode-map dired-mode-map)
+  (sr-highlight)
+  (dired-omit-mode dired-omit-mode)
+
+  (make-local-variable 'truncate-partial-width-windows)
+  (setq truncate-partial-width-windows (sr-truncate-v t))
+
+  (set (make-local-variable 'buffer-read-only) t)
+  (set (make-local-variable 'dired-header-face) 'sr-passive-path-face)
+  (set (make-local-variable 'dired-recursive-deletes) 'top)
+  (set (make-local-variable 'truncate-lines) nil)
+  (set (make-local-variable 'desktop-save-buffer) 'sr-desktop-save-buffer)
+  (set (make-local-variable 'revert-buffer-function) 'sr-revert-buffer)
+  (set (make-local-variable 'buffer-quit-function) 'sr-quit)
+  (set (make-local-variable 'sr-show-file-attributes) sr-show-file-attributes)
+  (set (make-local-variable 'hl-line-sticky-flag) nil)
+  (hl-line-mode 1)
+)
+
+;;;###autoload
+(define-derived-mode sr-virtual-mode dired-virtual-mode "Sunrise VIRTUAL"
+  "Sunrise Commander Virtual Mode. Useful for reusing find and locate results."
+  :group 'sunrise
+  (set-keymap-parent sr-virtual-mode-map sr-mode-map)
+  (sr-highlight)
+  (enriched-mode -1)
+
+  (make-local-variable 'truncate-partial-width-windows)
+  (setq truncate-partial-width-windows (sr-truncate-v t))
+
+  (set (make-local-variable 'buffer-read-only) t)
+  (set (make-local-variable 'dired-header-face) 'sr-passive-path-face)
+  (set (make-local-variable 'truncate-lines) nil)
+  (set (make-local-variable 'desktop-save-buffer) 'sr-desktop-save-buffer)
+  (set (make-local-variable 'revert-buffer-function) 'sr-revert-buffer)
+  (set (make-local-variable 'buffer-quit-function) 'sr-quit)
+  (set (make-local-variable 'sr-show-file-attributes) sr-show-file-attributes)
+  (set (make-local-variable 'hl-line-sticky-flag) nil)
+  (hl-line-mode 1)
+
+  (define-key sr-virtual-mode-map "\C-c\C-c" 'sr-virtual-dismiss))
+
+(defmacro sr-within (dir form)
+  "Evaluate FORM in Sunrise context."
+  `(unwind-protect
+       (progn
+         (setq sr-dired-directory
+               (file-name-as-directory (abbreviate-file-name ,dir)))
+         (ad-activate 'dired-find-buffer-nocreate)
+         ,form)
+     (ad-deactivate 'dired-find-buffer-nocreate)
+     (setq sr-dired-directory "")))
+
+(defmacro sr-save-aspect (&rest body)
+  "Restore omit mode, hidden attributes and point after a directory transition."
+  `(let ((inhibit-read-only t)
+         (omit (or dired-omit-mode -1))
+         (attrs (eval 'sr-show-file-attributes))
+         (path-faces sr-current-path-faces))
+     ,@body
+     (dired-omit-mode omit)
+     (if path-faces
+         (setq sr-current-path-faces path-faces))
+     (if (string= "NUMBER" (get sr-selected-window 'sorting-order))
+         (sr-sort-by-operation 'sr-numerical-sort-op))
+     (if (get sr-selected-window 'sorting-reverse)
+         (sr-reverse-pane))
+     (setq sr-show-file-attributes attrs)
+     (sr-display-attributes (point-min) (point-max) sr-show-file-attributes)
+     (sr-restore-point-if-same-buffer)))
+
+(defmacro sr-alternate-buffer (form)
+  "Execute FORM in a new buffer, after killing the previous one."
+  `(let ((dispose nil))
+     (unless (or (not (or dired-directory (eq major-mode 'sr-tree-mode)))
+                 (eq sr-left-buffer sr-right-buffer))
+       (setq dispose (current-buffer)))
+     ,form
+     (setq sr-this-directory default-directory)
+     (sr-keep-buffer)
+     (sr-highlight)
+     (when (and sr-kill-unused-buffers (buffer-live-p dispose))
+       (with-current-buffer dispose
+         (bury-buffer)
+         (set-buffer-modified-p nil)
+         (unless (kill-buffer dispose)
+           (kill-local-variable 'sr-current-path-faces))))))
+
+(defmacro sr-in-other (form)
+  "Execute FORM in the context of the passive pane.
+Helper macro for passive & synchronized navigation."
+  `(let ((home sr-selected-window))
+     (let ((sr-inhibit-highlight t))
+       (if sr-synchronized ,form)
+       (sr-change-window)
+       (condition-case description
+           ,form
+         (error (message (cadr description)))))
+     (if (not sr-running)
+         (sr-select-window home)
+       (run-hooks 'sr-refresh-hook)
+       (sr-change-window))))
+
+(defmacro sr-silently (&rest body)
+  "Inhibit calls to `message' in BODY."
+  `(cl-letf (((symbol-function 'message) (lambda (_msg &rest _args) (ignore))))
+     ,@body))
+
+(eval-and-compile
+  (defun sr-symbol (side type)
+    "Synthesize Sunrise symbols (`sr-left-buffer', `sr-right-window', etc.)."
+    (intern (concat "sr-" (symbol-name side) "-" (symbol-name type)))))
+
+(defun sr-dired-mode ()
+  "Set Sunrise mode in every Dired buffer opened in Sunrise (called in a hook)."
+  (if (and sr-running
+           (eq (selected-frame) sr-current-frame)
+           (sr-equal-dirs dired-directory default-directory)
+           (not (eq major-mode 'sr-mode)))
+      (let ((dired-listing-switches dired-listing-switches)
+            (sorting-options (or (get sr-selected-window 'sorting-options) "")))
+        (unless (and (featurep 'tramp)
+                     (string-match tramp-file-name-regexp default-directory))
+          (setq dired-listing-switches
+                (concat sr-listing-switches sorting-options)))
+        (sr-mode)
+        (dired-unadvertise dired-directory))))
+(add-hook 'dired-before-readin-hook 'sr-dired-mode)
+
+(defun sr-bookmark-jump ()
+  "Handle panes opened from bookmarks in Sunrise."
+  (when (and sr-running
+             (memq (selected-window) (list sr-left-window sr-right-window)))
+    (let ((last-buf (symbol-value (sr-symbol sr-selected-window 'buffer))))
+      (setq dired-omit-mode (with-current-buffer last-buf dired-omit-mode))
+      (setq sr-this-directory default-directory)
+      (if (sr-equal-dirs sr-this-directory sr-other-directory)
+          (sr-synchronize-panes t)
+        (revert-buffer))
+      (sr-keep-buffer)
+      (unless (memq last-buf (list (current-buffer) (sr-other 'buffer)))
+        (kill-buffer last-buf)))))
+(add-hook 'bookmark-after-jump-hook 'sr-bookmark-jump)
+
+(defun sr-virtualize-pane ()
+  "Put the current normal view in VIRTUAL mode."
+  (interactive)
+  (when (eq major-mode 'sr-mode)
+    (let ((focus (dired-get-filename 'verbatim t)))
+      (sr-save-aspect
+       (when (eq sr-left-buffer sr-right-buffer)
+         (dired default-directory)
+         (sr-keep-buffer))
+       (sr-virtual-mode))
+      (if focus (sr-focus-filename focus)))))
+
+(defun sr-virtual-dismiss ()
+  "Restore normal pane view in Sunrise VIRTUAL mode."
+  (interactive)
+  (when (eq major-mode 'sr-virtual-mode)
+    (let ((focus (dired-get-filename 'verbatim t)))
+      (sr-process-kill)
+      (sr-save-aspect
+       (sr-alternate-buffer (sr-goto-dir sr-this-directory))
+       (if focus (sr-focus-filename focus))
+       (revert-buffer)))))
+
+(defun sr-select-window (side)
+  "Select/highlight the given Sunrise window (right or left)."
+  (select-window (symbol-value (sr-symbol side 'window)))
+  (setq sr-selected-window side)
+  (setq sr-this-directory default-directory)
+  (sr-highlight))
+
+(defun sr-viewer-window ()
+  "Return an active window that can be used as the viewer."
+  (if (or (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+          (memq (current-buffer) (list sr-left-buffer sr-right-buffer)))
+      (let ((current-window (selected-window)) (target-window))
+        (dotimes (_times 2)
+          (setq current-window (next-window current-window))
+          (unless (memq current-window (list sr-left-window sr-right-window))
+            (setq target-window current-window)))
+        target-window)
+    (selected-window)))
+
+(defun sr-select-viewer-window (&optional force-setup)
+  "Select a window that is not a Sunrise pane.
+If no suitable active window can be found and FORCE-SETUP is set,
+calls the function `sr-setup-windows' and tries once again."
+  (interactive "p")
+  (let ((viewer (sr-viewer-window)))
+    (if (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+        (hl-line-mode 1))
+    (if viewer
+        (select-window viewer)
+      (when force-setup
+        (sr-setup-windows)
+        (select-window (sr-viewer-window))))))
+
+(defun sr-backup-buffer ()
+  "Create a backup copy of the current buffer.
+Used as a cache during revert operations."
+  (if (buffer-live-p sr-backup-buffer) (sr-kill-backup-buffer))
+  (let ((buf (current-buffer)))
+    (setq sr-backup-buffer (generate-new-buffer "*Sunrise Backup*"))
+    (with-current-buffer sr-backup-buffer
+      (insert-buffer-substring buf))
+    (run-hooks 'sr-refresh-hook)))
+
+(defun sr-kill-backup-buffer ()
+  "Kill the backup buffer associated to the current one, if there is any."
+  (when (buffer-live-p sr-backup-buffer)
+    (kill-buffer sr-backup-buffer)
+    (setq sr-backup-buffer nil)))
+(add-hook 'kill-buffer-hook       'sr-kill-backup-buffer)
+(add-hook 'change-major-mode-hook 'sr-kill-backup-buffer)
+
+(add-to-list 'enriched-translations '(invisible (t "x-invisible")))
+(defun sr-enrich-buffer ()
+  "Activate `enriched-mode' before saving a Sunrise buffer to a file.
+This is done so all its dired-filename attributes are kept in the file."
+  (if (memq major-mode '(sr-mode sr-virtual-mode))
+      (enriched-mode 1)))
+(add-hook 'before-save-hook 'sr-enrich-buffer)
+
+(defun sr-extend-with (extension &optional filename)
+  "Try to enhance Sunrise with EXTENSION (argument must be a symbol).
+An extension can be loaded from optional FILENAME. If found, the extension is
+immediately loaded, but only if `sr-autoload-extensions' is not nil."
+  (when sr-autoload-extensions
+    (require extension filename t)))
+
+(defadvice dired-find-buffer-nocreate
+  (before sr-advice-findbuffer (dirname &optional mode))
+  "A hack to avoid some Dired mode quirks in the Sunrise Commander."
+  (if (sr-equal-dirs sr-dired-directory dirname)
+      (setq mode 'sr-mode)))
+;; ^--- activated by sr-within macro
+
+(defadvice dired-dwim-target-directory
+  (around sr-advice-dwim-target ())
+  "Tweak the target directory guessing mechanism when Sunrise Commander is on."
+  (if (and sr-running (eq (selected-frame) sr-current-frame))
+      (setq ad-return-value sr-other-directory)
+    ad-do-it))
+(ad-activate 'dired-dwim-target-directory)
+
+(defadvice other-window
+  (around sr-advice-other-window (count &optional all-frames))
+  "Select the correct Sunrise Commander pane when switching from other windows."
+  (if (or (not sr-running) sr-ediff-on)
+      ad-do-it
+    (let ((from (selected-window)))
+      ad-do-it
+      (unless (or sr-traditional-other-window
+                  (memq from (list sr-left-window sr-right-window)))
+        ;; switching from outside
+        (sr-select-window sr-selected-window))
+      (with-no-warnings
+        (when (eq (selected-window) (sr-other 'window))
+          ;; switching from the other pane
+          (sr-change-window))))))
+(ad-activate 'other-window)
+
+(defadvice use-hard-newlines
+  (around sr-advice-use-hard-newlines (&optional arg insert))
+  "Stop asking if I want hard lines the in Sunrise Commander, just guess."
+  (if (memq major-mode '(sr-mode sr-virtual-mode))
+      (let ((inhibit-read-only t))
+        (setq insert 'guess)
+        ad-do-it)
+    ad-do-it))
+(ad-activate 'use-hard-newlines)
+
+(defadvice dired-insert-set-properties
+  (after sr-advice-dired-insert-set-properties (beg end))
+  "Manage hidden attributes in files added externally (e.g. from find-dired) to
+the Sunrise Commander."
+  (when (memq major-mode '(sr-mode sr-virtual-mode))
+    (with-no-warnings
+      (sr-display-attributes beg end sr-show-file-attributes))))
+(ad-activate 'dired-insert-set-properties)
+
+;;; ============================================================================
+;;; Sunrise Commander keybindings:
+
+(define-key sr-mode-map "\C-m"        'sr-advertised-find-file)
+(define-key sr-mode-map "f"           'sr-advertised-find-file)
+(define-key sr-mode-map "X"           'sr-advertised-execute-file)
+(define-key sr-mode-map "o"           'sr-quick-view)
+(define-key sr-mode-map "v"           'sr-quick-view)
+(define-key sr-mode-map "/"           'sr-goto-dir)
+(define-key sr-mode-map "j"           'sr-goto-dir)
+(define-key sr-mode-map "^"           'sr-dired-prev-subdir)
+(define-key sr-mode-map "J"           'sr-dired-prev-subdir)
+(define-key sr-mode-map ";"           'sr-follow-file)
+(define-key sr-mode-map "\M-t"        'sr-transpose-panes)
+(define-key sr-mode-map "\M-o"        'sr-synchronize-panes)
+(define-key sr-mode-map "\C-\M-o"     'sr-project-path)
+(define-key sr-mode-map "\M-y"        'sr-history-prev)
+(define-key sr-mode-map "\M-u"        'sr-history-next)
+(define-key sr-mode-map "\C-c>"       'sr-checkpoint-save)
+(define-key sr-mode-map "\C-c."       'sr-checkpoint-restore)
+(define-key sr-mode-map "\C-c\C-z"    'sr-sync)
+(define-key sr-mode-map "\C-c\C-c"    'revert-buffer)
+
+(define-key sr-mode-map "\t"          'sr-change-window)
+(define-key sr-mode-map "\C-c\t"      'sr-select-viewer-window)
+(define-key sr-mode-map "\M-a"        'sr-beginning-of-buffer)
+(define-key sr-mode-map "\M-e"        'sr-end-of-buffer)
+(define-key sr-mode-map "\C-c\C-s"    'sr-split-toggle)
+(define-key sr-mode-map "]"           'sr-enlarge-left-pane)
+(define-key sr-mode-map "["           'sr-enlarge-right-pane)
+(define-key sr-mode-map "}"           'sr-enlarge-panes)
+(define-key sr-mode-map "{"           'sr-shrink-panes)
+(define-key sr-mode-map "\\"          'sr-lock-panes)
+(define-key sr-mode-map "\C-c}"       'sr-max-lock-panes)
+(define-key sr-mode-map "\C-c{"       'sr-min-lock-panes)
+(define-key sr-mode-map "\C-o"        'dired-omit-mode)
+(define-key sr-mode-map "b"           'sr-browse-file)
+(define-key sr-mode-map "\C-c\C-w"    'sr-browse-pane)
+(define-key sr-mode-map "\C-c\d"      'sr-toggle-attributes)
+(define-key sr-mode-map "\M-l"        'sr-toggle-truncate-lines)
+(define-key sr-mode-map "s"           'sr-interactive-sort)
+(define-key sr-mode-map "r"           'sr-reverse-pane)
+(define-key sr-mode-map "\C-e"        'sr-scroll-up)
+(define-key sr-mode-map "\C-y"        'sr-scroll-down)
+(define-key sr-mode-map " "           'sr-scroll-quick-view)
+(define-key sr-mode-map "\M- "        'sr-scroll-quick-view-down)
+(define-key sr-mode-map [?\S- ]       'sr-scroll-quick-view-down)
+
+(define-key sr-mode-map "C"           'sr-do-copy)
+(define-key sr-mode-map "K"           'sr-do-clone)
+(define-key sr-mode-map "R"           'sr-do-rename)
+(define-key sr-mode-map "D"           'sr-do-delete)
+(define-key sr-mode-map "x"           'sr-do-flagged-delete)
+(define-key sr-mode-map "S"           'sr-do-symlink)
+(define-key sr-mode-map "Y"           'sr-do-relsymlink)
+(define-key sr-mode-map "H"           'sr-do-hardlink)
+(define-key sr-mode-map "\M-C"        'dired-do-copy)
+(define-key sr-mode-map "\M-R"        'dired-do-rename)
+(define-key sr-mode-map "\M-D"        'dired-do-delete)
+(define-key sr-mode-map "\M-S"        'dired-do-symlink)
+(define-key sr-mode-map "\M-Y"        'dired-do-relsymlink)
+(define-key sr-mode-map "\M-H"        'dired-do-hardlink)
+(define-key sr-mode-map "\C-x\C-q"    'sr-editable-pane)
+(define-key sr-mode-map "@"           'sr-fast-backup-files)
+(define-key sr-mode-map "\M-+"        'sr-create-files)
+
+(define-key sr-mode-map "="           'sr-diff)
+(define-key sr-mode-map "\C-c="       'sr-ediff)
+(define-key sr-mode-map "\C-x="       'sr-compare-panes)
+
+(define-key sr-mode-map "\C-c\C-f"    'sr-find)
+(define-key sr-mode-map "\C-c\C-n"    'sr-find-name)
+(define-key sr-mode-map "\C-c\C-g"    'sr-find-grep)
+(define-key sr-mode-map "\C-cb"       'sr-flatten-branch)
+(define-key sr-mode-map "\C-cp"       'sr-prune-paths)
+(define-key sr-mode-map "\C-c\C-l"    'sr-locate)
+(define-key sr-mode-map "\C-c/"       'sr-fuzzy-narrow)
+(define-key sr-mode-map "\C-c\C-r"    'sr-recent-files)
+(define-key sr-mode-map "\C-c\C-d"    'sr-recent-directories)
+(define-key sr-mode-map "\C-cv"       'sr-virtualize-pane)
+(define-key sr-mode-map "\C-c\C-v"    'sr-pure-virtual)
+(define-key sr-mode-map "Q"           'sr-do-query-replace-regexp)
+(define-key sr-mode-map "F"           'sr-do-find-marked-files)
+(define-key sr-mode-map "A"           'sr-do-search)
+(define-key sr-mode-map "\C-cs"       'sr-sticky-isearch-forward)
+(define-key sr-mode-map "\C-cr"       'sr-sticky-isearch-backward)
+(define-key sr-mode-map "\C-x\C-f"    'sr-find-file)
+(define-key sr-mode-map "y"           'sr-show-files-info)
+
+(define-key sr-mode-map "\M-n"        'sr-next-line-other)
+(define-key sr-mode-map [M-down]      'sr-next-line-other)
+(define-key sr-mode-map [A-down]      'sr-next-line-other)
+(define-key sr-mode-map "\M-p"        'sr-prev-line-other)
+(define-key sr-mode-map [M-up]        'sr-prev-line-other)
+(define-key sr-mode-map [A-up]        'sr-prev-line-other)
+(define-key sr-mode-map "\M-j"        'sr-goto-dir-other)
+(define-key sr-mode-map "\M-\C-m"     'sr-advertised-find-file-other)
+(define-key sr-mode-map "\M-f"        'sr-advertised-find-file-other)
+(define-key sr-mode-map "\C-c\C-m"    'sr-advertised-find-file-other)
+(define-key sr-mode-map "\M-^"        'sr-prev-subdir-other)
+(define-key sr-mode-map "\M-J"        'sr-prev-subdir-other)
+(define-key sr-mode-map "\M-m"        'sr-mark-other)
+(define-key sr-mode-map "\M-M"        'sr-unmark-backward-other)
+(define-key sr-mode-map "\M-U"        'sr-unmark-all-marks-other)
+(define-key sr-mode-map "\M-;"        'sr-follow-file-other)
+(define-key sr-mode-map "\C-\M-y"     'sr-history-prev-other)
+(define-key sr-mode-map "\C-\M-u"     'sr-history-next-other)
+
+(define-key sr-mode-map "\C-ct"       'sr-term)
+(define-key sr-mode-map "\C-cT"       'sr-term-cd)
+(define-key sr-mode-map "\C-c\C-t"    'sr-term-cd-newterm)
+(define-key sr-mode-map "\C-c\M-t"    'sr-term-cd-program)
+(define-key sr-mode-map "\C-c;"       'sr-follow-viewer)
+(define-key sr-mode-map "q"           'sr-quit)
+(define-key sr-mode-map "\C-xk"       'sr-kill-pane-buffer)
+(define-key sr-mode-map "\M-q"        'sunrise-cd)
+(define-key sr-mode-map "h"           'sr-describe-mode)
+(define-key sr-mode-map "?"           'sr-summary)
+(define-key sr-mode-map "k"           'dired-do-kill-lines)
+(define-key sr-mode-map [remap undo]  'sr-undo)
+(define-key sr-mode-map [remap undo-only] 'sr-undo)
+(define-key sr-mode-map [backspace]   'dired-unmark-backward)
+
+(define-key sr-mode-map [mouse-1]     'sr-mouse-advertised-find-file)
+(define-key sr-mode-map [mouse-2]     'sr-mouse-change-window)
+
+(define-key sr-mode-map [(control >)]         'sr-checkpoint-save)
+(define-key sr-mode-map [(control .)]         'sr-checkpoint-restore)
+(define-key sr-mode-map [(control tab)]       'sr-select-viewer-window)
+(define-key sr-mode-map [(control backspace)] 'sr-toggle-attributes)
+(define-key sr-mode-map [(control ?\=)]       'sr-ediff)
+(define-key sr-mode-map [(control meta ?\=)]  'sr-compare-panes)
+(define-key sr-mode-map [(control })]         'sr-max-lock-panes)
+(define-key sr-mode-map [(control {)]         'sr-min-lock-panes)
+
+(define-key sr-mode-map (kbd "<down-mouse-1>")  'ignore)
+
+(defvar sr-commander-keys
+  '(([(f2)]            . sr-goto-dir)
+    ([(f3)]            . sr-quick-view)
+    ([(f4)]            . sr-advertised-find-file)
+    ([(f5)]            . sr-do-copy)
+    ([(f6)]            . sr-do-rename)
+    ([(f7)]            . dired-create-directory)
+    ([(f8)]            . sr-do-delete)
+    ([(f10)]           . sr-quit)
+    ([(control f3)]    . sr-sort-by-name)
+    ([(control f4)]    . sr-sort-by-extension)
+    ([(control f5)]    . sr-sort-by-time)
+    ([(control f6)]    . sr-sort-by-size)
+    ([(control f7)]    . sr-sort-by-number)
+    ([(shift f7)]      . sr-do-symlink)
+    ([(insert)]        . sr-mark-toggle)
+    ([(control prior)] . sr-dired-prev-subdir))
+  "Traditional commander-style keybindings for the Sunrise Commander.")
+
+(defcustom sr-use-commander-keys t
+  "Whether to use traditional commander-style function keys (F5 = copy, etc)"
+  :group 'sunrise
+  :type 'boolean
+  :set (defun sr-set-commander-keys (symbol value)
+         "Setter function for the `sr-use-commander-keys' custom option."
+         (if value
+             (mapc (lambda (x)
+                     (define-key sr-mode-map (car x) (cdr x))) sr-commander-keys)
+           (mapc (lambda (x)
+                   (define-key sr-mode-map (car x) nil)) sr-commander-keys))
+         (set-default symbol value)))
+
+;;; ============================================================================
+;;; Initialization and finalization functions:
+
+;;;###autoload
+(defun sunrise (&optional left-directory right-directory filename)
+  "Toggle the Sunrise Commander file manager.
+If LEFT-DIRECTORY is given, the left window will display that
+directory (same for RIGHT-DIRECTORY). Specifying nil for any of
+these values uses the default, ie. $HOME."
+  (interactive)
+  (message "Starting Sunrise Commander...")
+
+  (if (not sr-running)
+      (let ((welcome sr-start-message))
+        (if left-directory
+            (setq sr-left-directory left-directory))
+        (if right-directory
+            (setq sr-right-directory right-directory))
+
+        (sr-switch-to-nonpane-buffer)
+        (setq sr-restore-buffer (current-buffer)
+              sr-current-frame (window-frame (selected-window))
+              sr-prior-window-configuration (current-window-configuration)
+              sr-running t)
+        (sr-setup-windows)
+        (if filename
+            (condition-case description
+                (sr-focus-filename (file-name-nondirectory filename))
+              (error (setq welcome (cadr description)))))
+        (setq sr-this-directory default-directory)
+        (message "%s" welcome)
+        (sr-highlight) ;;<-- W32Emacs needs this
+        (hl-line-mode 1))
+    (let ((my-frame (window-frame (selected-window))))
+      (sr-quit)
+      (message "All life leaps out to greet the light...")
+      (unless (eq my-frame (window-frame (selected-window)))
+        (select-frame my-frame)
+        (sunrise left-directory right-directory filename)))))
+;;;###autoload
+(defun sr-dired (&optional target switches)
+  "Visit the given target (file or directory) in `sr-mode'."
+  (interactive
+   (list
+    (read-file-name "Visit (file or directory): " nil nil nil)))
+  (let* ((target (expand-file-name (or target default-directory)))
+         (file (if (file-directory-p target) nil target))
+         (directory (if file (file-name-directory target) target))
+         (dired-omit-mode (if sr-show-hidden-files -1 1))
+         (sr-listing-switches (or switches sr-listing-switches)))
+    (unless (file-readable-p directory) 
+      (error "%s is not readable!" (sr-directory-name-proper directory)))
+    (unless (and sr-running (eq (selected-frame) sr-current-frame)) (sunrise))
+    (sr-select-window sr-selected-window)
+    (if file
+        (sr-follow-file file)
+      (sr-goto-dir directory))
+    (hl-line-mode 1)
+    (sr-display-attributes (point-min) (point-max) sr-show-file-attributes)
+    (sr-this 'buffer)))
+
+(defun sr-choose-cd-target ()
+  "Select a suitable target directory for cd operations."
+  (if (and sr-running (eq (selected-frame) sr-current-frame))
+      sr-this-directory
+    default-directory))
+
+;;;###autoload
+(defun sunrise-cd ()
+  "Toggle the Sunrise Commander FM keeping the current file in focus.
+If Sunrise is off, enable it and focus the file displayed in the current buffer.
+If Sunrise is on, disable it and switch to the buffer currently displayed in the
+viewer window."
+  (interactive)
+  (if (not (and sr-running
+                (eq (window-frame sr-left-window) (selected-frame))))
+      (sr-dired (or (buffer-file-name) (sr-choose-cd-target)))
+    (sr-quit t)
+    (message "Hast thou a charm to stay the morning-star in his steep course?")))
+
+(defun sr-this (&optional type)
+  "Return object of type TYPE corresponding to the active side of the manager.
+If TYPE is not specified (nil), returns a symbol (`left' or `right').
+If TYPE is `buffer' or `window', returns the corresponding buffer
+or window."
+  (if type
+      (symbol-value (sr-symbol sr-selected-window type))
+    sr-selected-window))
+
+(defun sr-other (&optional type)
+  "Return object of type TYPE corresponding to the passive side of the manager.
+If TYPE is not specified (nil), returns a symbol (`left' or `right').
+If TYPE is `buffer' or `window', returns the corresponding
+buffer or window."
+  (let ((side (cdr (assq sr-selected-window sr-side-lookup))))
+    (if type
+        (symbol-value (sr-symbol side type))
+      side)))
+
+;;; ============================================================================
+;;; Window management functions:
+
+(defmacro sr-setup-pane (side)
+  "Helper macro for the function `sr-setup-windows'."
+  `(let ((sr-selected-window ',side))
+     (setq ,(sr-symbol side 'window) (selected-window))
+     (if (buffer-live-p ,(sr-symbol side 'buffer))
+         (progn
+           (switch-to-buffer ,(sr-symbol side 'buffer))
+           (setq ,(sr-symbol side 'directory) default-directory))
+       (sr-dired ,(sr-symbol side 'directory)))))
+
+(defun sr-setup-visible-panes ()
+  "Set up sunrise on all visible panes."
+  (sr-setup-pane left)
+  (unless (eq sr-window-split-style 'top)
+    (other-window 1)
+    (sr-setup-pane right)))
+
+(defun sr-setup-windows()
+  "Set up the Sunrise window configuration (two windows in `sr-mode')."
+  (run-hooks 'sr-init-hook)
+  ;;get rid of all windows except one (not any of the panes!)
+  (sr-select-viewer-window)
+  (delete-other-windows)
+  (if (buffer-live-p other-window-scroll-buffer)
+      (switch-to-buffer other-window-scroll-buffer)
+    (sr-switch-to-nonpane-buffer))
+
+  ;;now create the viewer window
+  (unless (and sr-panes-height (< sr-panes-height (frame-height)))
+    (setq sr-panes-height (sr-get-panes-size)))
+  (if (and (<= sr-panes-height (* 2 window-min-height))
+           (eq sr-window-split-style 'vertical))
+      (setq sr-panes-height (* 2 window-min-height)))
+  (split-window (selected-window) sr-panes-height)
+
+  (case sr-window-split-style
+    (horizontal (split-window-horizontally))
+    (vertical   (split-window-vertically))
+    (top        (ignore))
+    (t (error "Unrecognised `sr-window-split-style' value: %s"
+              sr-window-split-style)))
+
+  (sr-setup-visible-panes)
+
+  ;;select the correct window
+  (sr-select-window sr-selected-window)
+  (sr-restore-panes-width)
+  (run-hooks 'sr-start-hook))
+
+(defun sr-switch-to-nonpane-buffer ()
+  "Try to switch to a buffer that is *not* a Sunrise pane."
+  (let ((start (current-buffer)))
+    (while (and
+              start
+              (or (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+                  (memq (current-buffer) (list sr-left-buffer sr-right-buffer))))
+        (bury-buffer)
+        (if (eq start (current-buffer)) (setq start nil)))))
+
+(defun sr-restore-prior-configuration ()
+  "Restore the configuration stored in `sr-prior-window-configuration' if any."
+  (set-window-configuration sr-prior-window-configuration)
+  (if (buffer-live-p sr-restore-buffer)
+      (set-buffer sr-restore-buffer)))
+
+(defun sr-lock-window (_frame)
+  "Resize the left Sunrise pane to have the \"right\" size."
+  (when sr-running
+    (if (not (window-live-p sr-left-window))
+        (setq sr-running nil)
+      (let ((sr-windows-locked sr-windows-locked))
+        (when (> window-min-height (- (frame-height)
+                                      (window-height sr-left-window)))
+          (setq sr-windows-locked nil))
+        (and sr-windows-locked
+             (not sr-ediff-on)
+             (not (eq sr-window-split-style 'vertical))
+             (window-live-p sr-left-window)
+             (save-selected-window
+               (select-window sr-left-window)
+               (let ((my-delta (- sr-panes-height (window-height))))
+                 (enlarge-window my-delta))
+               (scroll-right)
+               (when (window-live-p sr-right-window)
+                 (select-window sr-right-window)
+                 (scroll-right))))))))
+
+;; This keeps the size of the Sunrise panes constant:
+(add-hook 'window-size-change-functions 'sr-lock-window)
+
+(defun sr-highlight(&optional face)
+  "Set up the path line in the current buffer.
+With optional FACE, register this face as the current face to display the active
+path line."
+  (when (and (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+             (not sr-inhibit-highlight))
+    (let ((inhibit-read-only t))
+      (save-excursion
+        (goto-char (point-min))
+        (sr-hide-avfs-root)
+        (sr-highlight-broken-links)
+        (sr-graphical-highlight face)
+        (sr-force-passive-highlight)
+        (run-hooks 'sr-refresh-hook)))))
+
+(defun sr-unhighlight (face)
+  "Remove FACE from the list of faces of the active path line."
+  (when face
+    (setq sr-current-path-faces (delq face sr-current-path-faces))
+    (overlay-put sr-current-window-overlay 'face
+                 (or (car sr-current-path-faces) 'sr-active-path-face))))
+
+(defun sr-hide-avfs-root ()
+  "Hide the AVFS virtual filesystem root (if any) on the path line."
+  (if sr-avfs-root
+      (let ((start nil) (end nil)
+            (next (search-forward sr-avfs-root (point-at-eol) t)))
+        (if next (setq start (- next (length sr-avfs-root))))
+        (while next
+          (setq end (point)
+                next (search-forward sr-avfs-root (point-at-eol) t)))
+        (when end
+          (add-text-properties start end '(invisible t))))))
+
+(defun sr-highlight-broken-links ()
+  "Mark broken symlinks with an exclamation mark."
+  (let ((dired-marker-char ?!))
+    (while (search-forward-regexp dired-re-sym nil t)
+      (unless (or (not (eq 32 (char-after (line-beginning-position))))
+                  (file-exists-p (dired-get-filename)))
+        (dired-mark 1)))))
+
+(defsubst sr-invalid-overlayp ()
+  "Test for invalidity of the current buffer's graphical path line overlay.
+Returns t if the overlay is no longer valid and should be replaced."
+  (or (not (overlayp sr-current-window-overlay))
+      (eq (overlay-start sr-current-window-overlay)
+          (overlay-end sr-current-window-overlay))))
+
+(defun sr-graphical-highlight (&optional face)
+  "Set up the graphical path line in the current buffer.
+\(Fancy fonts and clickable path.)"
+  (let ((begin) (end) (inhibit-read-only t))
+
+    (when (sr-invalid-overlayp)
+      ;;determine begining and end
+      (save-excursion
+        (goto-char (point-min))
+        (search-forward-regexp "\\S " nil t)
+        (setq begin (1- (point)))
+        (end-of-line)
+        (setq end (1- (point))))
+
+      ;;build overlay
+      (when sr-current-window-overlay
+        (delete-overlay sr-current-window-overlay))
+      (set (make-local-variable 'sr-current-window-overlay)
+           (make-overlay begin end))
+
+      ;;path line hover effect:
+      (add-text-properties
+       begin
+       end
+       '(mouse-face sr-highlight-path-face
+                    help-echo "click to move up")
+       nil))
+    (when face
+      (setq sr-current-path-faces (cons face sr-current-path-faces)))
+    (overlay-put sr-current-window-overlay 'face
+                 (or (car sr-current-path-faces) 'sr-active-path-face))
+    (overlay-put sr-current-window-overlay 'window (selected-window))))
+
+(defun sr-force-passive-highlight (&optional revert)
+  "Set up the graphical path line in the passive pane.
+With optional argument REVERT, executes `revert-buffer' on the passive buffer."
+    (unless (or (not (buffer-live-p (sr-other 'buffer)))
+                (eq sr-left-buffer sr-right-buffer))
+      (with-current-buffer (sr-other 'buffer)
+        (when sr-current-window-overlay
+          (delete-overlay sr-current-window-overlay))
+        (when (and revert
+                   (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode)))
+          (revert-buffer)))))
+
+(defun sr-quit (&optional norestore)
+  "Quit Sunrise and restore Emacs to the previous state."
+  (interactive)
+  (if sr-running
+      (progn
+        (setq sr-running nil)
+        (sr-save-directories)
+        (sr-save-panes-width)
+        (if norestore
+            (progn
+              (sr-select-viewer-window)
+              (delete-other-windows))
+          (sr-restore-prior-configuration))
+        (sr-bury-panes)
+        (setq buffer-read-only nil)
+        (run-hooks 'sr-quit-hook)
+        (setq sr-current-frame nil))
+    (bury-buffer)))
+
+(add-hook 'delete-frame-functions
+          (lambda (frame)
+            (if (and sr-running (eq frame sr-current-frame)) (sr-quit))))
+
+(defun sr-save-directories ()
+  "Save current directories in the panes to use them at the next startup."
+  (when (window-live-p sr-left-window)
+    (set-buffer (window-buffer sr-left-window))
+    (when (memq major-mode '(sr-mode sr-tree-mode))
+      (setq sr-left-directory default-directory)
+      (setq sr-left-buffer (current-buffer))))
+
+  (when (window-live-p sr-right-window)
+    (set-buffer (window-buffer sr-right-window))
+    (when (memq major-mode '(sr-mode sr-tree-mode))
+      (setq sr-right-directory default-directory)
+      (setq sr-right-buffer (current-buffer)))))
+
+(defun sr-bury-panes ()
+  "Send both pane buffers to the end of the `buffer-list'."
+  (mapc (lambda (x)
+          (bury-buffer (symbol-value (sr-symbol x 'buffer))))
+        '(left right)))
+
+(defun sr-save-panes-width ()
+  "Save the width of the panes to use them at the next startup."
+  (unless sr-selected-window-width
+    (if (and (window-live-p sr-left-window)
+             (window-live-p sr-right-window))
+        (setq sr-selected-window-width
+              (window-width
+               (symbol-value (sr-symbol sr-selected-window 'window))))
+      (setq sr-selected-window-width t))))
+
+(defun sr-restore-panes-width ()
+  "Restore the last registered pane width."
+  (when (and (eq sr-window-split-style 'horizontal)
+             (numberp sr-selected-window-width))
+    (enlarge-window-horizontally
+     (min (- sr-selected-window-width (window-width))
+          (- (frame-width) (window-width) window-min-width)))))
+
+(defun sr-resize-panes (&optional reverse)
+  "Enlarge (or shrink, if REVERSE is t) the left pane by 5 columns."
+  (when (and (window-live-p sr-left-window)
+             (window-live-p sr-right-window))
+    (let ((direction (or (and reverse -1) 1)))
+      (save-selected-window
+        (select-window sr-left-window)
+        (enlarge-window-horizontally (* 5 direction))))
+    (setq sr-selected-window-width nil)))
+
+(defun sr-enlarge-left-pane ()
+  "Enlarge the left pane by 5 columns."
+  (interactive)
+  (when (< (1+ window-min-width) (window-width sr-right-window))
+      (sr-resize-panes)
+      (sr-save-panes-width)))
+
+(defun sr-enlarge-right-pane ()
+  "Enlarge the right pane by 5 columns."
+  (interactive)
+  (when (< (1+ window-min-width) (window-width sr-left-window))
+      (sr-resize-panes t)
+      (sr-save-panes-width)))
+
+(defun sr-get-panes-size (&optional size)
+  "Tell what the maximal, minimal and normal pane sizes should be."
+  (let ((frame (frame-height)))
+    (case size
+      (max (max (- frame window-min-height 1) 5))
+      (min (min (1+ window-min-height) 5))
+      (t  (/ (* sr-windows-default-ratio (frame-height)) 100)))))
+
+(defun sr-enlarge-panes ()
+  "Enlarge both panes vertically."
+  (interactive)
+  (let ((sr-windows-locked nil)
+        (max (sr-get-panes-size 'max))
+        (ratio 1)
+        delta)
+    (save-selected-window
+      (when (eq sr-window-split-style 'vertical)
+        (select-window sr-right-window)
+        (setq ratio 2)
+        (setq delta (- max (window-height)))
+        (if (> (/ max ratio) (window-height))
+            (shrink-window (if (< 2 delta) -2 -1))))
+      (select-window sr-left-window)
+      (if (> (/ max ratio) (window-height))
+          (shrink-window -1))
+      (setq sr-panes-height (* (window-height) ratio)))))
+
+(defun sr-shrink-panes ()
+  "Shink both panes vertically."
+  (interactive)
+  (let ((sr-windows-locked nil)
+        (min (sr-get-panes-size 'min))
+        (ratio 1)
+        delta)
+    (save-selected-window
+      (when (eq sr-window-split-style 'vertical)
+        (select-window sr-right-window)
+        (setq ratio 2)
+        (setq delta (- (window-height) min))
+        (if (< min (window-height))
+            (shrink-window (if (< 2 delta) 2 1))))
+      (select-window sr-left-window)
+      (if (< min (window-height))
+          (shrink-window 1))
+      (setq sr-panes-height (* (window-height) ratio)))))
+
+(defun sr-lock-panes (&optional height)
+  "Resize and lock the panes at some vertical position.
+The optional argument determines the height to lock the panes at.
+Valid values are `min' and `max'; given any other value, locks
+the panes at normal position."
+  (interactive)
+  (if sr-running
+    (if (not (and (window-live-p sr-left-window)
+                  (or (window-live-p sr-right-window)
+                      (eq sr-window-split-style 'top))))
+        (sr-setup-windows)
+      (setq sr-panes-height (sr-get-panes-size height))
+      (let ((locked sr-windows-locked))
+        (setq sr-windows-locked t)
+        (if height
+            (shrink-window 1)
+          (setq sr-selected-window-width t)
+          (balance-windows))
+        (unless locked
+          (sit-for 0.1)
+          (setq sr-windows-locked nil))))
+    (sunrise)))
+
+(defun sr-max-lock-panes ()
+  (interactive)
+  (sr-save-panes-width)
+  (sr-lock-panes 'max))
+
+(defun sr-min-lock-panes ()
+  (interactive)
+  (sr-save-panes-width)
+  (sr-lock-panes 'min))
+
+;;; ============================================================================
+;;; File system navigation functions:
+
+(defun sr-advertised-find-file (&optional filename)
+  "Handle accesses to file system objects through the user interface.
+Includes cases when the user presses return, f or clicks on the path line."
+  (interactive)
+  (unless filename
+    (if (eq 1 (line-number-at-pos)) ;; <- Click or Enter on path line.
+        (let* ((path (buffer-substring (point) (point-at-eol)))
+               (levels (1- (length (split-string path "/")))))
+          (if (< 0 levels)
+              (sr-dired-prev-subdir levels)
+            (sr-beginning-of-buffer)))
+      (setq filename (dired-get-filename nil t)
+            filename (and filename (expand-file-name filename)))))
+  (if filename
+      (if (file-exists-p filename)
+          (sr-find-file filename)
+        (error "Sunrise: nonexistent target"))))
+
+(defun sr-advertised-execute-file (&optional prefix)
+  "Execute the currently selected file in a new subprocess."
+  (interactive "P")
+  (let ((path (dired-get-filename nil t)) (label) (args))
+    (if path
+        (setq label  (file-name-nondirectory path))
+      (error "Sunrise: no executable file on this line"))
+    (unless (and (not (file-directory-p path)) (file-executable-p path))
+      (error "Sunrise: \"%s\" is not an executable file" label))
+    (when prefix
+      (setq args (read-string (format "arguments for \"%s\": " label))
+            label (format "%s %s" label args)))
+    (message "Sunrise: executing \"%s\" in new process" label)
+    (if args
+        (apply #'start-process (append (list "Sunrise Subprocess" nil path)
+                                       (split-string args)))
+      (start-process "Sunrise Subprocess" nil path))))
+
+(defun sr-find-file (filename &optional wildcards)
+  "Determine the proper way of handling an object in the file system.
+FILENAME can be either a regular file, a regular directory, a
+Sunrise VIRTUAL directory, or a virtual directory served by
+AVFS."
+  (interactive (find-file-read-args "Find file or directory: " nil))
+  (cond ((file-directory-p filename) (sr-find-regular-directory filename))
+        ((and (sr-avfs-directory-p filename) (sr-avfs-dir filename))
+         (sr-find-regular-directory (sr-avfs-dir filename)))
+        ((sr-virtual-directory-p filename) (sr-find-virtual-directory filename))
+        (t (sr-find-regular-file filename wildcards))))
+
+(defun sr-virtual-directory-p (filename)
+  "Tell whether FILENAME is the path to a Sunrise VIRTUAL directory."
+  (eq 'sr-virtual-mode (assoc-default filename auto-mode-alist 'string-match)))
+
+(defun sr-avfs-directory-p (filename)
+  "Tell whether FILENAME can be seen as the root of an AVFS virtual directory."
+  (let ((mode (assoc-default filename auto-mode-alist 'string-match)))
+    (and sr-avfs-root
+         (or (eq 'archive-mode mode)
+             (eq 'tar-mode mode)
+             (and (listp mode) (eq 'jka-compr (cadr mode)))
+             (not (equal "." (sr-assoc-key filename
+                                           sr-avfs-handlers-alist
+                                           'string-match)))))))
+
+(defun sr-find-regular-directory (directory)
+  "Visit the given regular directory in the active pane."
+  (setq directory (file-name-as-directory directory))
+  (let ((parent (expand-file-name "../")))
+    (if (and (not (sr-equal-dirs parent default-directory))
+             (sr-equal-dirs directory parent))
+        (sr-dired-prev-subdir)
+      (sr-goto-dir directory))))
+
+(defun sr-find-virtual-directory (sr-virtual-dir)
+  "Visit the given Sunrise VIRTUAL directory in the active pane."
+  (sr-save-aspect
+   (sr-alternate-buffer (find-file sr-virtual-dir)))
+  (sr-history-push sr-virtual-dir)
+  (set-visited-file-name nil t)
+  (sr-keep-buffer)
+  (sr-backup-buffer))
+
+(defun sr-find-regular-file (filename &optional wildcards)
+  "Deactivate Sunrise and visit FILENAME as a regular file with WILDCARDS.
+\(See `find-file' for more details on wildcard expansion.)"
+  (condition-case description
+      (let ((buff (find-file-noselect filename nil nil wildcards)))
+        (sr-save-panes-width)
+        (sr-quit)
+        (set-window-configuration sr-prior-window-configuration)
+        (switch-to-buffer buff))
+    (error (message "%s" (cadr description)))))
+
+(defun sr-avfs-dir (filename)
+  "Return the virtual path for accessing FILENAME through AVFS.
+Returns nil if AVFS cannot manage this kind of file."
+  (let* ((handler (assoc-default filename sr-avfs-handlers-alist 'string-match))
+         (vdir (concat filename handler)))
+    (unless (sr-overlapping-paths-p sr-avfs-root vdir)
+      (setq vdir (concat sr-avfs-root vdir)))
+    (if (file-attributes vdir) vdir nil)))
+
+(defun sr-goto-dir (dir)
+  "Change the current directory in the active pane to the given one."
+  (interactive "DChange directory (file or pattern): ")
+  (if sr-goto-dir-function
+      (funcall sr-goto-dir-function dir)
+    (unless (and (eq major-mode 'sr-mode) (sr-equal-dirs dir default-directory))
+      (if (and sr-avfs-root
+               (null (posix-string-match "#" dir)))
+          (setq dir (replace-regexp-in-string
+                     (expand-file-name sr-avfs-root) "" dir)))
+      (sr-save-aspect
+       (sr-within dir (sr-alternate-buffer (dired dir))))
+      (sr-history-push default-directory)
+      (sr-beginning-of-buffer))))
+
+(defun sr-dired-prev-subdir (&optional count)
+  "Go to the parent directory, or COUNT subdirectories upwards."
+  (interactive "P")
+  (unless (sr-equal-dirs default-directory "/")
+    (let* ((count (or count 1))
+           (to (replace-regexp-in-string "x" "../" (make-string count ?x)))
+           (from (expand-file-name (substring to 1)))
+           (from (sr-directory-name-proper from))
+           (from (replace-regexp-in-string "\\(?:#.*/?$\\|/$\\)" "" from))
+           (to (replace-regexp-in-string "\\.\\./$" "" (expand-file-name to))))
+      (sr-goto-dir to)
+      (unless (sr-equal-dirs from to)
+        (sr-focus-filename from)))))
+
+(defun sr-follow-file (&optional target-path)
+  "Go to the same directory where the selected file is.
+Very useful inside Sunrise VIRTUAL buffers."
+  (interactive)
+  (if (null target-path)
+      (setq target-path (dired-get-filename nil t)))
+
+  (let ((target-dir (file-name-directory target-path))
+        (target-symlink (file-symlink-p target-path))
+        (target-file))
+
+    ;; if the target is a symlink and there's nothing more interesting to do
+    ;; then follow the symlink:
+    (when (and target-symlink
+               (string= target-dir (dired-current-directory))
+               (not (eq major-mode 'sr-virtual-mode)))
+      (unless (file-exists-p target-symlink)
+        (error "Sunrise: file is a symlink to a nonexistent target"))
+      (setq target-path target-symlink)
+      (setq target-dir (file-name-directory target-symlink)))
+
+    (setq target-file (file-name-nondirectory target-path))
+
+    (when target-dir ;; <-- nil in symlinks to other files in same directory:
+      (setq target-dir (sr-chop ?/ target-dir))
+      (sr-goto-dir target-dir))
+    (sr-focus-filename target-file)))
+
+(defun sr-follow-viewer ()
+  "Go to the directory of the file displayed in the viewer window."
+  (interactive)
+  (when sr-running
+    (let* ((viewer (sr-viewer-window))
+           (viewer-buffer (if viewer (window-buffer viewer)))
+           (target-dir) (target-file))
+      (when viewer-buffer
+        (with-current-buffer viewer-buffer
+          (setq target-dir default-directory
+                target-file (sr-directory-name-proper (buffer-file-name)))))
+      (sr-select-window sr-selected-window)
+      (if target-dir (sr-goto-dir target-dir))
+      (if target-file (sr-focus-filename target-file)))))
+
+(defun sr-project-path ()
+  "Find projections of the active directory over the passive one.
+
+Locates interactively all descendants of the directory in the passive pane that
+have a path similar to the directory in the active pane.
+
+For instance, if the active pane is displaying directory /a/b/c and the passive
+one is displaying /x/y, this command will check for the existence of any of the
+following: /x/y/a/b/c, /x/y/b/c, /x/y/c and /x/y. Each (existing) directory
+located according to this schema will be known hereafter as a 'projection of the
+directory /a/b/c over /x/y'.
+
+If many projections of the active directory over the passive one exist, one can
+rotate among all of them by invoking `sr-project-path' repeatedly : they will be
+visited in order, from longest path to shortest."
+
+  (interactive)
+  (let* ((sr-synchronized nil)
+         (path (sr-chop ?/ (expand-file-name (dired-current-directory))))
+         (pos (if (< 0 (length path)) 1)) (candidate) (next-key))
+    (while pos
+      (setq candidate (concat sr-other-directory (substring path pos))
+            pos (string-match "/" path (1+ pos))
+            pos (if pos (1+ pos)))
+      (when (and (file-directory-p candidate)
+                 (not (sr-equal-dirs sr-this-directory candidate)))
+        (sr-goto-dir-other candidate)
+        (setq next-key (read-key-sequence "(press C-M-o again for more)"))
+        (if (eq (lookup-key sr-mode-map next-key) 'sr-project-path)
+            (sr-history-prev-other)
+          (setq unread-command-events (listify-key-sequence next-key)
+                pos nil))))
+    (unless next-key
+      (message "Sunrise: sorry, no suitable projections found"))))
+
+(defun sr-history-push (element)
+  "Push a new path into the history stack of the current pane."
+  (unless (or (null element)
+              (and (featurep 'tramp)
+                   (string-match tramp-file-name-regexp element)))
+    (let* ((pane (assoc sr-selected-window sr-history-registry))
+           (hist (cdr pane))
+           (len (length hist)))
+      (if (>= len sr-history-length)
+          (nbutlast hist (- len sr-history-length)))
+      (setq element (abbreviate-file-name (sr-chop ?/ element))
+            hist (delete element hist))
+      (push element hist)
+      (setcdr pane hist))
+    (sr-history-stack-reset)))
+
+(defun sr-history-next ()
+  "Navigate forward in the history of the active pane."
+  (interactive)
+  (let ((side (assoc sr-selected-window sr-history-stack)))
+    (unless (zerop (cadr side))
+      (sr-history-move -1))
+    (when (zerop (cadr side))
+      (sr-history-stack-reset))))
+
+(defun sr-history-prev ()
+  "Navigate backwards in the history of the active pane."
+  (interactive)
+  (let ((history (cdr (assoc sr-selected-window sr-history-registry)))
+        (stack (cdr (assoc sr-selected-window sr-history-stack))))
+    (when (< (abs (cdr stack)) (1- (length history)))
+      (sr-history-move 1))))
+
+(defun sr-history-move (step)
+  "Traverse the history of the active pane in a stack-like fashion.
+This function re-arranges the history list of the current pane so as to make it
+simulate a stack of directories, from which one can 'pop' the current directory
+and 'push' it back, keeping the most recently visited entries always near the
+top of the stack."
+  (let* ((side (assoc sr-selected-window sr-history-stack))
+         (depth (cadr side)) (goal) (target-dir))
+    (when (> 0 (* step depth))
+      (sr-history-stack-reset))
+    (setq goal  (1+ (cddr side))
+          depth (* step (+ (abs depth) step))
+          target-dir (sr-history-pick goal))
+    (when target-dir
+      (sr-goto-dir target-dir)
+      (setcdr side (cons depth goal)))))
+
+(defun sr-history-stack-reset ()
+  "Reset the current history stack counter."
+  (let ((side (assoc sr-selected-window sr-history-stack)))
+    (setcdr side '(0 . 0))))
+
+(defun sr-history-pick (position)
+  "Return directory at POSITION in current history.
+If the entry was removed or made inaccessible since our last visit, remove it
+from the history list and check among the previous ones until an accessible
+directory is found, or the list runs out of entries."
+  (let* ((history (cdr (assoc sr-selected-window sr-history-registry)))
+         (target (nth position history)))
+    (while (and target (not (file-accessible-directory-p target)))
+      (delete target history)
+      (setq target (nth position history)))
+    target))
+
+(defun sr-require-checkpoints-extension (&optional noerror)
+  "Bootstrap code for checkpoint support.
+Just tries to require the appropriate checkpoints extension
+depending on the version of bookmark.el being used."
+  (require 'bookmark nil t)
+  (let* ((feature
+          (cond ((fboundp 'bookmark-make-record) 'sunrise-x-checkpoints)
+                (t 'sunrise-x-old-checkpoints)))
+         (name (symbol-name feature)))
+    (or
+     (not (featurep 'sunrise-commander))
+     (require feature nil t)
+     noerror
+     (error "Feature `%s' not found!\
+For checkpoints to work, download http://joseito.republika.pl/%s.el.gz\
+and add it to your `load-path'" name name))))
+
+(defmacro sr-checkpoint-command (function-name)
+  `(defun ,function-name (&optional arg)
+     (interactive)
+     (sr-require-checkpoints-extension)
+     (if (commandp #',function-name)
+         (call-interactively #',function-name)
+       (funcall #',function-name arg))))
+(sr-checkpoint-command sr-checkpoint-save)
+(sr-checkpoint-command sr-checkpoint-restore)
+(sr-checkpoint-command sr-checkpoint-handler)
+;;;###autoload (autoload 'sr-checkpoint-handler "sunrise-commander" "" t)
+
+(defun sr-do-find-marked-files (&optional noselect)
+  "Sunrise replacement for `dired-do-find-marked-files'."
+  (interactive "P")
+  (let* ((files (delq nil (mapcar (lambda (x)
+                                    (and (file-regular-p x) x))
+                                  (dired-get-marked-files)))))
+    (unless files
+      (error "Sunrise: no regular files to open"))
+    (unless noselect (sr-quit))
+    (dired-simultaneous-find-file files noselect)))
+
+;;; ============================================================================
+;;; Graphical interface interaction functions:
+
+(defun sr-change-window()
+  "Change to the other Sunrise pane."
+  (interactive)
+  (if (and (window-live-p sr-left-window) (window-live-p sr-right-window))
+      (let ((here sr-this-directory))
+        (setq sr-this-directory sr-other-directory)
+        (setq sr-other-directory here)
+        (sr-select-window (sr-other)))))
+
+(defun sr-mouse-change-window (e)
+  "Change to the Sunrise pane clicked in by the mouse."
+  (interactive "e")
+  (mouse-set-point e)
+  (if (eq (selected-window) (sr-other 'window))
+      (sr-change-window)))
+
+(defun sr-beginning-of-buffer()
+  "Go to the first directory/file in Dired."
+  (interactive)
+  (goto-char (point-min))
+  (when (re-search-forward directory-listing-before-filename-regexp nil t)
+    (dotimes (_times 2)
+      (when (looking-at "\.\.?/?$")
+        (dired-next-line 1)))))
+
+(defun sr-end-of-buffer()
+  "Go to the last directory/file in Dired."
+  (interactive)
+  (goto-char (point-max))
+  (re-search-backward directory-listing-before-filename-regexp)
+  (dired-next-line 0))
+
+(defun sr-focus-filename (filename)
+  "Try to select FILENAME in the current buffer."
+  (if (and dired-omit-mode
+           (string-match (dired-omit-regexp) filename))
+      (dired-omit-mode -1))
+  (let ((sr-inhibit-highlight t)
+        (expr (sr-chop ?/ filename)))
+    (cond ((file-symlink-p filename)
+           (setq expr (concat (regexp-quote expr) " ->")))
+          ((file-directory-p filename)
+           (setq expr (concat (regexp-quote expr) "\\(?:/\\|$\\)")))
+          ((file-regular-p filename)
+           (setq expr (concat (regexp-quote expr) "$"))))
+    (setq expr (concat "[0-9] +" expr))
+    (beginning-of-line)
+    (unless (re-search-forward expr nil t)
+      (re-search-backward expr nil t)))
+  (beginning-of-line)
+  (re-search-forward directory-listing-before-filename-regexp nil t))
+
+(defun sr-split-toggle()
+  "Change Sunrise window layout from horizontal to vertical to top and so on."
+  (interactive)
+  (case sr-window-split-style
+    (horizontal (sr-split-setup 'vertical))
+    (vertical (sr-split-setup 'top))
+    (top (progn
+           (sr-split-setup 'horizontal)
+           (sr-in-other (revert-buffer))))
+    (t (sr-split-setup 'horizontal))))
+
+(defun sr-split-setup(split-type)
+  (setq sr-window-split-style split-type)
+  (when sr-running
+    (when (eq sr-window-split-style 'top)
+      (sr-select-window 'left)
+      (delete-window sr-right-window)
+      (setq sr-panes-height (window-height)))
+    (sr-setup-windows))
+  (message "Sunrise: split style changed to \"%s\"" (symbol-name split-type)))
+
+(defun sr-transpose-panes ()
+  "Change the order of the panes."
+  (interactive)
+  (unless (eq sr-left-buffer sr-right-buffer)
+    (mapc (lambda (x)
+            (let ((left (sr-symbol 'left x)) (right (sr-symbol 'right x)) (tmp))
+              (setq tmp (symbol-value left))
+              (set left (symbol-value right))
+              (set right tmp)))
+          '(directory buffer window))
+    (let ((tmp sr-this-directory))
+      (setq sr-this-directory sr-other-directory
+            sr-other-directory tmp))
+    (select-window sr-right-window)
+    (sr-setup-visible-panes)
+    (sr-select-window sr-selected-window)))
+
+(defun sr-synchronize-panes (&optional reverse)
+  "Change the directory in the other pane to that in the current one.
+If the optional parameter REVERSE is non-nil, performs the
+opposite operation, ie. changes the directory in the current pane
+to that in the other one."
+  (interactive "P")
+  (let ((target (current-buffer)) (sr-inhibit-highlight t))
+    (sr-change-window)
+    (if reverse
+        (setq target (current-buffer))
+      (sr-alternate-buffer (switch-to-buffer target))
+      (sr-history-push default-directory))
+    (sr-change-window)
+    (when reverse
+      (sr-alternate-buffer (switch-to-buffer target))
+      (sr-history-push default-directory)
+      (revert-buffer)))
+  (sr-highlight))
+
+(defun sr-browse-pane ()
+  "Browse the directory in the active pane."
+  (interactive)
+  (if (not (featurep 'browse-url))
+      (error "Sunrise: feature `browse-url' not available!")
+    (let ((url (concat "file://" (expand-file-name default-directory))))
+      (message "Browsing directory %s " default-directory)
+      (if (featurep 'w3m)
+          (eval '(w3m-goto-url url))
+        (browse-url url)))))
+
+(defun sr-browse-file (&optional file)
+  "Display the selected file in the default web browser."
+  (interactive)
+  (unless (featurep 'browse-url)
+    (error "ERROR: Feature browse-url not available!"))
+  (setq file (or file (dired-get-filename)))
+  (save-selected-window
+    (sr-select-viewer-window)
+    (let ((buff (current-buffer)))
+      (browse-url (concat "file://" file))
+      (unless (eq buff (current-buffer))
+        (sr-scrollable-viewer (current-buffer)))))
+  (message "Browsing \"%s\" in web browser" file))
+
+(defun sr-revert-buffer (&optional _ignore-auto _no-confirm)
+  "Revert the current pane using the contents of the backup buffer (if any).
+If the buffer is non-virtual the backup buffer is killed."
+  (interactive)
+  (if (buffer-live-p sr-backup-buffer)
+      (let ((marks (dired-remember-marks (point-min) (point-max)))
+            (focus (dired-get-filename 'verbatim t))
+            (inhibit-read-only t))
+        (erase-buffer)
+        (insert-buffer-substring sr-backup-buffer)
+        (sr-beginning-of-buffer)
+        (dired-mark-remembered marks)
+        (if focus (sr-focus-filename focus))
+        (dired-change-marks ?\t ?*)
+        (if (eq 'sr-mode major-mode) (sr-kill-backup-buffer)))
+    (unless (or (eq major-mode 'sr-virtual-mode)
+                (local-variable-p 'sr-virtual-buffer))
+      (dired-revert)
+      (if (string= "NUMBER" (get sr-selected-window 'sorting-order))
+          (sr-sort-by-number t)
+        (if (get sr-selected-window 'sorting-reverse)
+            (sr-reverse-pane)))))
+  (sr-display-attributes (point-min) (point-max) sr-show-file-attributes)
+  (sr-highlight))
+
+(defun sr-kill-pane-buffer ()
+  "Kill the buffer currently displayed in the active pane, or quit Sunrise.
+Custom variable `sr-kill-unused-buffers' controls whether unused buffers are
+killed automatically by Sunrise when the user navigates away from the directory
+they contain. When this flag is set, all requests to kill the current buffer are
+managed by just calling `sr-quit'."
+  (interactive)
+  (if sr-kill-unused-buffers
+      (sr-quit)
+    (kill-buffer (current-buffer))
+    (let ((_x (pop (cdr (assoc sr-selected-window sr-history-registry)))))
+      (sr-history-stack-reset))))
+
+(defun sr-quick-view (&optional arg)
+  "Quickly view the currently selected item.
+On regular files, opens the file in quick-view mode (see `sr-quick-view-file'
+for more details), on directories, visits the selected directory in the passive
+pane, and on symlinks follows the file the link points to in the passive pane.
+With optional argument kills the last quickly viewed file without opening a new
+buffer."
+  (interactive "P")
+  (if arg
+      (sr-quick-view-kill)
+    (let ((name (dired-get-filename nil t)))
+      (cond ((file-directory-p name) (sr-quick-view-directory name))
+            ((file-symlink-p name) (sr-quick-view-symlink name))
+            (t (sr-quick-view-file))))))
+
+(defun sr-quick-view-kill ()
+  "Kill the last buffer opened using quick view (if any)."
+  (let ((buf other-window-scroll-buffer))
+    (when (and (buffer-live-p buf)
+               (or (not sr-confirm-kill-viewer)
+                   (y-or-n-p (format "Kill buffer %s? " (buffer-name buf)))))
+      (setq other-window-scroll-buffer nil)
+      (save-window-excursion (kill-buffer buf)))))
+
+(defun sr-quick-view-directory (name)
+  "Open the directory NAME in the passive pane."
+  (let ((name (expand-file-name name)))
+    (sr-in-other (sr-advertised-find-file name))))
+
+(defun sr-quick-view-symlink (name)
+  "Follow the target of the symlink NAME in the passive pane."
+  (let ((name (expand-file-name (file-symlink-p name))))
+    (if (file-exists-p name)
+        (sr-in-other (sr-follow-file name))
+      (error "Sunrise: file is a symlink to a nonexistent target"))))
+
+(defun sr-quick-view-file ()
+  "Open the selected file on the viewer window without selecting it.
+Kills any other buffer opened previously the same way."
+  (let ((split-width-threshold (* 10 (window-width)))
+        (filename (expand-file-name (dired-get-filename nil t))))
+    (save-selected-window
+      (condition-case description
+          (progn
+            (sr-select-viewer-window)
+            (find-file filename)
+            (if (and (not (eq (current-buffer) other-window-scroll-buffer))
+                          (buffer-live-p other-window-scroll-buffer))
+                (kill-buffer other-window-scroll-buffer))
+            (sr-scrollable-viewer (current-buffer)))
+        (error (message "%s" (cadr description)))))))
+
+;; These clean up after a quick view:
+(add-hook 'sr-quit-hook (defun sr-sr-quit-function ()
+                          (setq other-window-scroll-buffer nil)))
+(add-hook 'kill-buffer-hook
+          (defun sr-kill-viewer-function ()
+            (if (eq (current-buffer) other-window-scroll-buffer)
+                (setq other-window-scroll-buffer  nil))))
+
+(defun sr-mask-attributes (beg end)
+  "Manage the hiding of attributes in region from BEG to END.
+Selective hiding of specific attributes can be controlled by customizing the
+`sr-attributes-display-mask' variable."
+  (let ((cursor beg) props)
+    (cl-labels ((sr-make-display-props
+            (display-function-or-flag)
+            (cond ((functionp display-function-or-flag)
+                   `(display
+                     ,(apply display-function-or-flag
+                             (list (buffer-substring cursor (1- (point)))))))
+                  ((null display-function-or-flag) '(invisible t))
+                  (t nil))))
+      (if sr-attributes-display-mask
+          (block block 
+            (mapc (lambda (do-display)
+                    (search-forward-regexp "\\w")
+                    (search-forward-regexp "\\s-")
+                    (forward-char -1)
+                    (setq props (sr-make-display-props do-display))
+                    (when props 
+                      (add-text-properties cursor (point) props))
+                    (setq cursor (point))
+                    (if (>= (point) end) (return-from block)))
+                  sr-attributes-display-mask))
+        (unless (>= cursor end)
+          (add-text-properties cursor (1- end) '(invisible t)))))))
+
+(defun sr-display-attributes (beg end visiblep)
+  "Manage the display of file attributes in the region from BEG to END.
+if VISIBLEP is nil then shows file attributes in region, otherwise hides them."
+  (let ((inhibit-read-only t) (next))
+    (save-excursion
+      (goto-char beg)
+      (forward-line -1)
+      (while (and (null next) (< (point) end))
+        (forward-line 1)
+        (setq next (dired-move-to-filename)))
+      (while (and next (< next end))
+        (beginning-of-line)
+        (forward-char 1)
+        (if (not visiblep)
+            (sr-mask-attributes (point) next)
+          (remove-text-properties (point) next '(invisible t))
+          (remove-text-properties (point) next '(display)))
+        (forward-line 1)
+        (setq next (dired-move-to-filename))))))
+
+(defun sr-toggle-attributes ()
+  "Hide/Show the attributes of all files in the active pane."
+  (interactive)
+  (setq sr-show-file-attributes (not sr-show-file-attributes))
+  (sr-display-attributes (point-min) (point-max) sr-show-file-attributes))
+
+(defun sr-toggle-truncate-lines ()
+  "Enable/Disable truncation of long lines in the active pane."
+  (interactive)
+  (if (sr-truncate-p)
+      (progn
+        (setq truncate-partial-width-windows (sr-truncate-v nil))
+        (message "Sunrise: wrapping long lines"))
+    (progn
+      (setq truncate-partial-width-windows (sr-truncate-v t))
+      (message "Sunrise: truncating long lines")))
+  (sr-silently (dired-do-redisplay)))
+
+(defun sr-truncate-p ()
+  "Return non-nil if `truncate-partial-width-windows' affects the current pane.
+Used by `sr-toggle-truncate-lines'."
+  (if (numberp truncate-partial-width-windows)
+      (< 0 truncate-partial-width-windows)
+    truncate-partial-width-windows))
+
+(defun sr-truncate-v (active)
+  "Return the appropriate value for `truncate-partial-width-widows'.
+Depends on the Emacs version being used. Used by
+`sr-toggle-truncate-lines'."
+  (or (and (version<= "23" emacs-version)
+           (or (and active 3000) 0))
+      active))
+
+(defun sr-sort-order (label option)
+  "Change the sorting order of the active pane.
+Appends additional options to `dired-listing-switches' and
+reverts the buffer."
+  (if (eq major-mode 'sr-virtual-mode)
+      (sr-sort-virtual option)
+    (progn
+      (put sr-selected-window 'sorting-order label)
+      (put sr-selected-window 'sorting-options option)
+      (let ((dired-listing-switches dired-listing-switches))
+        (unless (string-match "^/ftp:" default-directory)
+          (setq dired-listing-switches sr-listing-switches))
+        (dired-sort-other (concat dired-listing-switches option) t))
+      (revert-buffer)))
+  (message "Sunrise: sorting entries by %s" label))
+
+(defmacro sr-defun-sort-by (postfix options)
+  "Helper macro for defining `sr-sort-by-xxx' functions."
+  `(defun ,(intern (format "sr-sort-by-%s" postfix)) ()
+     ,(format "Sorts the contents of the current Sunrise pane by %s." postfix)
+     (interactive)
+     (sr-sort-order ,(upcase postfix) ,options)))
+(sr-defun-sort-by "name" "")
+(sr-defun-sort-by "extension" "X")
+(sr-defun-sort-by "time" "t")
+(sr-defun-sort-by "size" "S")
+
+(defun sr-sort-by-number (&optional inhibit-label)
+  "Sort the contents of the current Sunrise pane numerically.
+Displays entries containing unpadded numbers in a more logical
+order than when sorted alphabetically by name."
+  (interactive)
+  (sr-sort-by-operation 'sr-numerical-sort-op (unless inhibit-label "NUMBER"))
+  (if (get sr-selected-window 'sorting-reverse) (sr-reverse-pane)))
+
+(defun sr-interactive-sort (order)
+  "Prompt for a new sorting order for the active pane and apply it."
+  (interactive "cSort by (n)ame, n(u)mber, (s)ize, (t)ime or e(x)tension? ")
+  (if (>= order 97)
+      (setq order (- order 32)))
+  (case order
+    (?U (sr-sort-by-number))
+    (?T (sr-sort-by-time))
+    (?S (sr-sort-by-size))
+    (?X (sr-sort-by-extension))
+    (t  (sr-sort-by-name))))
+
+(defun sr-reverse-pane (&optional interactively)
+  "Reverse the contents of the active pane."
+  (interactive "p")
+  (let ((line (line-number-at-pos))
+        (reverse (get sr-selected-window 'sorting-reverse)))
+    (sr-sort-by-operation 'identity)
+    (when interactively
+      (put sr-selected-window 'sorting-reverse (not reverse))
+      (goto-char (point-min)) (forward-line (1- line))
+      (re-search-forward directory-listing-before-filename-regexp nil t))))
+
+(defun sr-sort-virtual (option)
+  "Manage sorting of buffers in Sunrise VIRTUAL mode."
+  (let ((opt (string-to-char option)) (inhibit-read-only t) (beg) (end))
+    (case opt
+      (?X (sr-end-of-buffer)
+          (setq end (point-at-eol))
+          (sr-beginning-of-buffer)
+          (setq beg (point-at-bol))
+          (sort-regexp-fields nil "^.*$" "[/.][^/.]+$" beg end))
+      (?t (sr-sort-by-operation
+           (lambda (x) (sr-attribute-sort-op 5 t x)) "TIME"))
+      (?S (sr-sort-by-operation
+           (lambda (x) (sr-attribute-sort-op 7 t x)) "SIZE"))
+      (t (sr-sort-by-operation
+          (lambda (x) (sr-attribute-sort-op -1 nil x)) "NAME")))))
+
+(defun sr-sort-by-operation (operation &optional label)
+  "General function for reordering the contents of a Sunrise pane.
+OPERATION is a function that receives a list produced by
+`sr-build-sort-lists', reorders it in some way, transforming it
+into a list that can be passed to `sort-reorder', so the records
+in the current buffer are reordered accordingly. The LABEL is a
+string that will be used to set the sorting order of the current
+pane and then displayed in the minibuffer; if it's not provided
+or its value is nil then the ordering enforced by this function
+is transient and can be undone by reverting the pane, or by
+moving it to a different directory. See `sr-numerical-sort-op'
+and `sr-attribute-sort-op' for examples of OPERATIONs."
+  (interactive)
+  (let ((messages (> (- (point-max) (point-min)) 50000))
+        (focus (dired-get-filename 'verbatim t))
+        (inhibit-read-only t))
+    (if messages (message "Finding sort keys..."))
+    (let* ((sort-lists (sr-build-sort-lists))
+           (old (reverse sort-lists))
+           (beg) (end))
+      (if messages (message "Sorting records..."))
+      (setq sort-lists (apply operation (list sort-lists)))
+      (if messages (message "Reordering buffer..."))
+      (save-excursion
+        (save-restriction
+          (sr-end-of-buffer)
+          (setq end (point-at-eol))
+          (sr-beginning-of-buffer)
+          (setq beg (point-at-bol))
+          (narrow-to-region beg end)
+          (sort-reorder-buffer sort-lists old)))
+      (if messages (message "Reordering buffer... Done")))
+    (sr-highlight)
+    (if focus (sr-focus-filename focus))
+    (when label
+      (put sr-selected-window 'sorting-order label)
+      (message "Sunrise: sorting entries by %s" label)))
+  nil)
+
+(defun sr-numerical-sort-op (sort-lists)
+  "Strategy used to numerically sort contents of a Sunrise pane.
+Used by `sr-sort-by-operation'. See `sr-sort-by-number' for more
+on this kind of sorting."
+  (mapcar
+   'cddr
+   (sort
+    (sort
+     (mapcar
+      (lambda (x)
+        (let ((key (buffer-substring-no-properties (car x) (cddr x))))
+          (append
+           (list key
+                 (string-to-number (replace-regexp-in-string "^[^0-9]*" "" key))
+                 (cdr x))
+           (cdr x))))
+      sort-lists)
+     (lambda (a b) (string< (car a) (car b))))
+    (lambda (a b) (< (cadr a) (cadr b))))))
+
+(defun sr-attribute-sort-op (nth-attr as-number sort-lists)
+  "Strategy used to sort contents of a Sunrise pane according to file attributes.
+Used by `sr-sort-by-operation'. See `file-attributes' for a list
+of supported attributes and their positions. Directories are
+forced to remain always on top. NTH-ATTR is the position of the
+attribute to use for sorting, or -1 for the name of the file.
+AS-NUMBER determines whether comparisons will be numeric or
+alphabetical. SORT-LISTS is a list of positions obtained from
+`sr-build-sort-lists'."
+  (let ((attributes (sr-files-attributes))
+        (zero (if as-number 0 "")))
+    (mapcar
+     'cddr
+     (sort
+      (sort
+       (mapcar
+        (lambda (x)
+          (let* ((key (buffer-substring-no-properties (car x) (cddr x)))
+                 (key (sr-chop ?/ (replace-regexp-in-string " -> .*$" "" key)))
+                 (attrs (assoc-default key attributes))
+                 (index))
+            (when attrs
+              (setq attrs (apply 'cons attrs)
+                    index (or (nth (1+ nth-attr) attrs) zero))
+              (append (list (cadr attrs) index (cdr x)) (cdr x)))))
+        sort-lists)
+       (lambda (a b) (sr-compare nth-attr (cadr b) (cadr a))))
+      (lambda (a b)
+        (if (and (car a) (car b))
+            (sr-compare nth-attr (cadr b) (cadr a))
+          (and (car a) (not (stringp (car a))))))))))
+
+(defun sr-build-sort-lists ()
+  "Analyse contents of the current Sunrise pane for `sr-sort-by-operation'.
+Builds a list of dotted lists of the form (a b . c) -- where 'a'
+is the position at the start of the file name in an entry, while
+'b' and 'c' are the start and end positions of the whole entry.
+These lists are used by `sr-sort-by-operation' to sort the
+contents of the pane in arbitrary ways."
+  (delq nil
+        (mapcar
+         (lambda (x) (and (atom (car x)) x))
+         (save-excursion
+           (sr-beginning-of-buffer)
+           (beginning-of-line)
+           (sort-build-lists 'forward-line 'end-of-line 'dired-move-to-filename
+                             nil)))))
+
+(defun sr-compare (mode a b)
+  "General comparison function, used to sort files in VIRTUAL buffers.
+MODE must be a number; if it is less than 0, the direction of the
+comparison is inverted: (sr-compare -1 a b) === (sr-compare 1
+b a). Compares numbers using `<', strings case-insensitively
+using `string<' and lists recursively until the first two
+elements that are non-equal are found."
+  (if (< mode 0) (let (tmp) (setq tmp a a b b tmp mode (abs mode))))
+  (cond ((or (null a) (null b)) nil)
+        ((and (listp a) (listp b)) (if (= (car a) (car b))
+                                       (sr-compare mode (cdr a) (cdr b))
+                                     (sr-compare mode (car a) (car b))))
+        ((and (stringp a) (stringp b)) (string< (downcase a) (downcase b)))
+        ((and (numberp a) (numberp b)) (< a b))
+        (t nil)))
+
+(defun sr-scroll-up ()
+  "Scroll the current pane or (if active) the viewer pane 1 line up."
+  (interactive)
+  (if (buffer-live-p other-window-scroll-buffer)
+      (save-selected-window
+        (sr-select-viewer-window)
+        (scroll-up 1))
+    (scroll-up 1)))
+
+(defun sr-scroll-down ()
+  "Scroll the current pane or (if active) the viewer pane 1 line down."
+  (interactive)
+  (if (buffer-live-p other-window-scroll-buffer)
+      (save-selected-window
+        (sr-select-viewer-window)
+        (scroll-down 1))
+    (scroll-down 1)))
+
+(defun sr-scroll-quick-view ()
+  "Scroll down the viewer window during a quick view."
+  (interactive)
+  (if other-window-scroll-buffer (scroll-other-window)))
+
+(defun sr-scroll-quick-view-down ()
+  "Scroll down the viewer window during a quick view."
+  (interactive)
+  (if other-window-scroll-buffer (scroll-other-window-down nil)))
+
+(defun sr-undo ()
+  "Restore selection as it was before the last file operation."
+  (interactive)
+  (dired-undo)
+  (sr-highlight))
+
+;;; ============================================================================
+;;; Passive & synchronized navigation functions:
+
+(defun sr-sync ()
+  "Toggle the Sunrise synchronized navigation feature."
+  (interactive)
+  (setq sr-synchronized (not sr-synchronized))
+  (mapc 'sr-mark-sync (list sr-left-buffer sr-right-buffer))
+  (message "Sunrise: sync navigation is now %s" (if sr-synchronized "ON" "OFF"))
+  (run-hooks 'sr-refresh-hook)
+  (sr-in-other (run-hooks 'sr-refresh-hook)))
+
+(defun sr-mark-sync (&optional buffer)
+  "Change `mode-name' depending on whether synchronized navigation is enabled."
+  (save-window-excursion
+    (if buffer
+        (switch-to-buffer buffer))
+    (setq mode-name (concat "Sunrise "
+                            (if sr-synchronized "SYNC-NAV" "Commander")))))
+
+;; This advertises synchronized navigation in all new buffers:
+(add-hook 'sr-mode-hook 'sr-mark-sync)
+
+(defun sr-next-line-other ()
+  "Move the cursor down in the passive pane."
+  (interactive)
+  (sr-in-other (dired-next-line 1)))
+
+(defun sr-prev-line-other ()
+  "Move the cursor up in the passive pane."
+  (interactive)
+  (sr-in-other (dired-next-line -1)))
+
+(defun sr-goto-dir-other (dir)
+  "Change the current directory in the passive pane to the given one."
+  (interactive (list (read-directory-name
+                      "Change directory in PASSIVE pane (file or pattern): "
+                      sr-other-directory)))
+  (sr-in-other (sr-goto-dir dir)))
+
+(defun sr-advertised-find-file-other ()
+  "Open the file/directory selected in the passive pane."
+  (interactive)
+  (if sr-synchronized
+      (let ((target (sr-directory-name-proper (dired-get-filename))))
+        (sr-change-window)
+        (if (file-directory-p target)
+            (sr-goto-dir (expand-file-name target))
+          (if (y-or-n-p "Unable to synchronize. Disable sync navigation? ")
+              (sr-sync)))
+        (sr-change-window)
+        (sr-advertised-find-file))
+    (sr-in-other (sr-advertised-find-file))))
+
+(defun sr-mouse-advertised-find-file (e)
+  "Open the file/directory pointed to by the mouse."
+  (interactive "e")
+  (sr-mouse-change-window e)
+  (sr-advertised-find-file))
+
+(defun sr-prev-subdir-other (&optional count)
+  "Go to the previous subdirectory in the passive pane."
+  (interactive "P")
+  (let ((count (or count 1)))
+    (sr-in-other (sr-dired-prev-subdir count))))
+
+(defun sr-follow-file-other ()
+  "Go to the directory of the selected file, but in the passive pane."
+  (interactive)
+  (let ((filename (dired-get-filename nil t)))
+    (sr-in-other (sr-follow-file filename))))
+
+(defun sr-history-prev-other ()
+  "Change to previous directory (if any) in the passive pane's history list."
+  (interactive)
+  (sr-in-other (sr-history-prev)))
+
+(defun sr-history-next-other ()
+  "Change to the next directory (if any) in the passive pane's history list."
+  (interactive)
+  (sr-in-other (sr-history-next)))
+
+(defun sr-mark-other (arg)
+  "Mark the current (or next ARG) files in the passive pane."
+  (interactive "P")
+  (setq arg (or arg 1))
+  (sr-in-other (dired-mark arg)))
+
+(defun sr-unmark-backward-other (arg)
+  (interactive "p")
+  (sr-in-other (dired-unmark-backward arg)))
+
+(defun sr-unmark-all-marks-other ()
+  "Remove all marks from the passive pane."
+  (interactive)
+  (sr-in-other (dired-unmark-all-marks)))
+
+;;; ============================================================================
+;;; Progress feedback functions:
+
+(defun sr-progress-prompt (op-name)
+  "Build the default progress feedback message."
+  (concat "Sunrise: " op-name "... "))
+
+(defun sr-make-progress-reporter (op-name totalsize)
+  "Make a new Sunrise progress reporter.
+Prepends two integers (accumulator and scale) to a standard
+progress reporter (built using `make-progress-reporter' from
+subr.el): accumulator keeps the current state of the reporter,
+and scale is used when the absolute value of 100% is bigger than
+`most-positive-fixnum'."
+  (let ((accumulator 0) (scale 1) (maxval totalsize))
+    (when (> totalsize most-positive-fixnum)
+      (setq scale (/ totalsize most-positive-fixnum))
+      (setq maxval most-positive-fixnum))
+    (list accumulator scale
+          (make-progress-reporter
+           (sr-progress-prompt op-name) 0 maxval 0 1 0.5))))
+
+(defun sr-progress-reporter-update (reporter size)
+  "Update REPORTER (a Sunrise progress reporter) by adding SIZE to its state."
+  (let ((scale (cadr reporter)))
+    (setcar reporter (+ (truncate (/ size scale)) (car reporter)))
+    (progress-reporter-update (car (cddr reporter)) (car reporter))))
+
+(defun sr-progress-reporter-done (reporter)
+  "Print REPORTER's feedback message followed by \"done\" in echo area."
+  (progress-reporter-done (car (cddr reporter))))
+
+;;; ============================================================================
+;;; File manipulation functions:
+
+(defun sr-create-files (&optional qty)
+  "Interactively create empty file(s) with the given name or template.
+Optional prefix argument specifies the number of files to create.
+*NEVER* overwrites existing files. A template may contain one
+%-sequence like those used by `format', but the only supported
+specifiers are: d (decimal), x (hex) or o (octal)."
+  (interactive "p")
+  (let* ((qty (or (and (integerp qty) (< 0 qty) qty) 1))
+         (prompt (if (>= 1 qty) "Create file: "
+                   (format "Create %d files using template: " qty)))
+         (filename (read-file-name prompt)) (name))
+    (with-temp-buffer
+      (if (>= 1 qty)
+          (unless (file-exists-p filename) (write-file filename))
+        (unless (string-match "%[0-9]*[dox]" filename)
+          (setq filename (concat filename ".%d")))
+        (setq filename (replace-regexp-in-string "%\\([^%]\\)" "%%\\1" filename)
+              filename (replace-regexp-in-string
+                        "%%\\([0-9]*[dox]\\)" "%\\1" filename))
+        (dotimes (n qty)
+          (setq name (format filename (1+ n)))
+          (unless (file-exists-p name) (write-file name)))))
+    (sr-revert-buffer)))
+
+(defun sr-editable-pane ()
+  "Put the current pane in File Names Editing mode (`wdired-mode')."
+  (interactive)
+  (sr-graphical-highlight 'sr-editing-path-face)
+  (let* ((was-virtual (eq major-mode 'sr-virtual-mode))
+         (major-mode 'dired-mode))
+    (wdired-change-to-wdired-mode)
+    (if was-virtual
+        (set (make-local-variable 'sr-virtual-buffer) t)))
+  (run-hooks 'sr-refresh-hook))
+
+(defun sr-readonly-pane (as-virtual)
+  "Put the current pane back in Sunrise mode."
+  (when as-virtual
+    (sr-virtual-mode)
+    (sr-force-passive-highlight t))
+  (dired-build-subdir-alist)
+  (sr-revert-buffer))
+
+(defmacro sr-protect-terminate-wdired (&rest body)
+  "Compile the `cl-letf' forms used in `sr-terminate-wdired'.
+This macro allows interpreted code to work without requiring
+cl-macs at runtime."
+  `(cl-letf (((symbol-function 'yes-or-no-p) (lambda (prompt) (ignore)))
+          ((symbol-function 'revert-buffer)
+           (lambda (&optional ignore-auto noconfirm preserve-modes))
+           (ignore)))
+     ,@body))
+
+(defun sr-terminate-wdired (fun)
+  "Restore the current pane's original mode after editing with WDired."
+  (ad-add-advice
+   fun
+   (ad-make-advice
+    (intern (concat "sr-advice-" (symbol-name fun))) nil t
+    `(advice
+      lambda ()
+      (if (not sr-running)
+          ad-do-it
+        (let ((was-virtual (local-variable-p 'sr-virtual-buffer))
+              (saved-point (point)))
+          (sr-save-aspect
+           (setq major-mode 'wdired-mode)
+           (sr-protect-terminate-wdired ad-do-it)
+           (sr-readonly-pane was-virtual)
+           (goto-char saved-point))
+          (sr-unhighlight 'sr-editing-path-face)))))
+   'around 'last)
+  (ad-activate fun nil))
+(sr-terminate-wdired 'wdired-finish-edit)
+(sr-terminate-wdired 'wdired-abort-changes)
+
+(defun sr-do-copy ()
+  "Copy selected files and directories recursively to the passive pane."
+  (interactive)
+  (let* ((items (dired-get-marked-files nil))
+         (vtarget (sr-virtual-target))
+         (target (or vtarget sr-other-directory))
+         (progress))
+    (if (and (not vtarget) (sr-equal-dirs default-directory sr-other-directory))
+        (dired-do-copy)
+      (when (sr-ask "Copy" target items #'y-or-n-p)
+        (if vtarget
+            (progn
+              (sr-copy-virtual)
+              (message "Done: %d items(s) copied" (length items)))
+          (progn
+            (setq progress (sr-make-progress-reporter
+                            "copying" (sr-files-size items)))
+            (sr-clone items target #'copy-file progress ?C)
+            (sr-progress-reporter-done progress)))
+        (sr-silently (dired-unmark-all-marks))))))
+
+(defun sr-do-symlink ()
+  "Symlink selected files or directories from one pane to the other."
+  (interactive)
+  (if (sr-equal-dirs default-directory sr-other-directory)
+      (dired-do-symlink)
+    (sr-link #'make-symbolic-link "Symlink" dired-keep-marker-symlink)))
+
+(defun sr-do-relsymlink ()
+  "Symlink selected files or directories from one pane to the other relatively.
+See `dired-make-relative-symlink'."
+  (interactive)
+  (if (sr-equal-dirs default-directory sr-other-directory)
+      (dired-do-relsymlink)
+    (sr-link #'dired-make-relative-symlink
+             "RelSymLink"
+             dired-keep-marker-relsymlink)))
+
+(defun sr-do-hardlink ()
+  "Same as `dired-do-hardlink', but refuse to hardlink files to VIRTUAL buffers."
+  (interactive)
+  (if (sr-virtual-target)
+      (error "Cannot hardlink files to a VIRTUAL buffer, try (C)opying instead")
+    (dired-do-hardlink)))
+
+(defun sr-do-rename ()
+  "Move selected files and directories recursively from one pane to the other."
+  (interactive)
+  (when (sr-virtual-target)
+    (error "Cannot move files to a VIRTUAL buffer, try (C)opying instead"))
+  (if (sr-equal-dirs default-directory sr-other-directory)
+      (dired-do-rename)
+    (let ((marked (dired-get-marked-files)))
+      (when (sr-ask "Move" sr-other-directory marked #'y-or-n-p)
+        (let ((names (mapcar #'file-name-nondirectory marked))
+              (progress (sr-make-progress-reporter "renaming" (length marked)))
+              (inhibit-read-only t))
+          (sr-in-other
+           (progn
+             (sr-move-files marked default-directory progress)
+             (revert-buffer)
+             (when (eq major-mode 'sr-mode)
+               (dired-mark-remembered
+                (mapcar (lambda (x) (cons (expand-file-name x) ?R)) names))
+               (sr-focus-filename (car names)))))
+          (sr-progress-reporter-done progress))
+        (sr-silently (revert-buffer))))))
+
+(defun sr-do-delete ()
+  "Remove selected files from the file system."
+  (interactive)
+  (let* ((files (dired-get-marked-files))
+         (mode (sr-ask "Delete" nil files #'sr-y-n-or-a-p))
+         (deletion-mode (cond ((eq mode 'ALWAYS) 'always)
+                              (mode 'top)
+                              (t (error "(No deletions performed)")))))
+    (mapc (lambda (x)
+            (message "Deleting %s" x)
+            (dired-delete-file x deletion-mode)) files)
+    (if (eq major-mode 'sr-virtual-mode)
+        (dired-do-kill-lines)
+      (revert-buffer))))
+
+(defun sr-do-flagged-delete ()
+  "Remove flagged files from the file system."
+  (interactive)
+  (let* ((dired-marker-char dired-del-marker)
+         (regexp (dired-marker-regexp)) )
+    (if (save-excursion (goto-char (point-min))
+                        (re-search-forward regexp nil t))
+        (sr-do-delete)
+      (message "(No deletions requested)"))))
+
+(defun sr-do-clone (&optional mode)
+  "Clone all selected items recursively into the passive pane."
+  (interactive "cClone as: (D)irectories only, (C)opies, (H)ardlinks,\
+ (S)ymlinks or (R)elative symlinks? ")
+
+  (if (sr-virtual-target)
+      (error "Cannot clone into a VIRTUAL buffer, try (C)opying instead"))
+  (if (sr-equal-dirs default-directory sr-other-directory)
+      (error "Cannot clone inside one single directory, please select a\
+ different one in the passive pane"))
+
+  (let ((target sr-other-directory) clone-op items progress)
+    (if (and mode (>= mode 97)) (setq mode (- mode 32)))
+    (setq clone-op
+          (case mode
+            (?D nil)
+            (?C #'copy-file)
+            (?H #'add-name-to-file)
+            (?S #'make-symbolic-link)
+            (?R #'dired-make-relative-symlink)
+            (t (error "Invalid cloning mode: %c" mode))))
+    (setq items (dired-get-marked-files nil))
+    (setq progress (sr-make-progress-reporter
+                    "cloning" (sr-files-size items)))
+    (sr-clone items target clone-op progress ?K)
+    (dired-unmark-all-marks)
+    (message "Done: %d items(s) dispatched" (length items))))
+
+(defun sr-fast-backup-files ()
+  "Make backup copies of all marked files inside the same directory.
+The extension to append to each filename can be controlled by
+setting the value of the `sr-fast-backup-extension' custom
+variable. Directories are not copied."
+  (interactive)
+  (let ((extension (if (listp sr-fast-backup-extension)
+                       (eval sr-fast-backup-extension)
+                     sr-fast-backup-extension)))
+    (dired-do-copy-regexp "$" extension))
+  (revert-buffer))
+
+(defun sr-clone (items target clone-op progress mark-char)
+  "Clone all given items (files and dirs) recursively into the passive pane."
+  (let ((names (mapcar #'file-name-nondirectory items))
+        (inhibit-read-only t))
+    (with-current-buffer (sr-other 'buffer)
+      (sr-clone-files items target clone-op progress))
+    (when (window-live-p (sr-other 'window))
+      (sr-in-other
+       (progn
+         (revert-buffer)
+         (when (memq major-mode '(sr-mode sr-virtual-mode))
+           (dired-mark-remembered
+            (mapcar (lambda (x) (cons (expand-file-name x) mark-char)) names))
+           (sr-focus-filename (car names))))))))
+
+(defun sr-clone-files (file-paths target-dir clone-op progress &optional do-overwrite)
+  "Clone all files in FILE-PATHS to TARGET-DIR using CLONE-OP to clone the files.
+FILE-PATHS should be a list of full paths."
+  (setq target-dir (replace-regexp-in-string "/?$" "/" target-dir))
+  (mapc
+   (function
+    (lambda (f)
+      (sr-progress-reporter-update progress (nth 7 (file-attributes f)))
+      (let* ((name (file-name-nondirectory f))
+             (target-file (concat target-dir name))
+             (symlink-to (file-symlink-p (sr-chop ?/ f)))
+             (clone-args (list f target-file t)))
+        (cond
+         (symlink-to
+          (progn
+            (if (file-exists-p symlink-to)
+                (setq symlink-to (expand-file-name symlink-to)))
+            (make-symbolic-link symlink-to target-file do-overwrite)))
+
+         ((file-directory-p f)
+          (let ((initial-path (file-name-directory f)))
+            (unless (file-symlink-p initial-path)
+              (sr-clone-directory
+               initial-path name target-dir clone-op progress do-overwrite))))
+
+         (clone-op
+          ;; (message "[[Cloning: %s => %s]]" f target-file)
+          (if (eq clone-op 'copy-file)
+              (setq clone-args
+                    (append clone-args (list dired-copy-preserve-time))))
+          (if (file-exists-p target-file)
+              (if (or (eq do-overwrite 'ALWAYS)
+                      (setq do-overwrite (sr-ask-overwrite target-file)))
+                  (apply clone-op clone-args))
+            (apply clone-op clone-args)))))))
+   file-paths))
+
+(defun sr-clone-directory (in-dir d to-dir clone-op progress do-overwrite)
+  "Clone directory IN-DIR/D and all its files recursively to TO-DIR.
+IN-DIR/D => TO-DIR/D using CLONE-OP to clone the files."
+  (setq d (replace-regexp-in-string "/?$" "/" d))
+  (if (string= "" d)
+      (setq to-dir (concat to-dir (sr-directory-name-proper in-dir))))
+  (let* ((files-in-d (sr-list-of-contents (concat in-dir d)))
+         (file-paths-in-d
+          (mapcar (lambda (f) (concat in-dir d f)) files-in-d)))
+    (unless (file-exists-p (concat to-dir d))
+      (make-directory (concat to-dir d)))
+    (sr-clone-files file-paths-in-d (concat to-dir d) clone-op progress do-overwrite)))
+
+(defsubst sr-move-op (file target-dir progress do-overwrite)
+  "Helper function used by `sr-move-files' to rename files and directories."
+  (condition-case nil
+      (dired-rename-file file target-dir do-overwrite)
+    (error
+     (sr-clone-directory file "" target-dir 'copy-file progress do-overwrite)
+     (dired-delete-file file 'always))))
+
+(defun sr-move-files (file-path-list target-dir progress &optional do-overwrite)
+  "Move all files in FILE-PATH-LIST (list of full paths) to TARGET-DIR."
+  (mapc
+   (function
+    (lambda (f)
+      (if (file-directory-p f)
+          (progn
+            (setq f (replace-regexp-in-string "/?$" "/" f))
+            (sr-progress-reporter-update progress 1)
+            (sr-move-op f target-dir progress do-overwrite))
+        (let* ((name (file-name-nondirectory f))
+               (target-file (concat target-dir name)))
+          ;; (message "Renaming: %s => %s" f target-file)
+          (sr-progress-reporter-update progress 1)
+          (if (file-exists-p target-file)
+              (if (or (eq do-overwrite 'ALWAYS)
+                      (setq do-overwrite (sr-ask-overwrite target-file)))
+                  (dired-rename-file f target-file t))
+            (dired-rename-file f target-file t)) ))))
+   file-path-list))
+
+(defun sr-link (creator action marker)
+  "Helper function for implementing `sr-do-symlink' and `sr-do-relsymlink'."
+  (if (sr-virtual-target)
+      (error "Cannot link files to a VIRTUAL buffer, try (C)opying instead.")
+    (dired-create-files creator action (dired-get-marked-files nil)
+                        (lambda (from)
+                          (setq from (sr-chop ?/ from))
+                          (if (file-directory-p from)
+                              (setq from (sr-directory-name-proper from))
+                            (setq from (file-name-nondirectory from)))
+                          (expand-file-name from sr-other-directory))
+                        marker)))
+
+(defun sr-virtual-target ()
+  "If the passive pane is in VIRTUAL mode, return its name as a string.
+Otherwise returns nil."
+  (save-window-excursion
+    (switch-to-buffer (sr-other 'buffer))
+    (if (eq major-mode 'sr-virtual-mode)
+        (or (buffer-file-name) "Sunrise VIRTUAL buffer")
+      nil)))
+
+(defun sr-copy-virtual ()
+  "Manage copying of files or directories to buffers in VIRTUAL mode."
+  (let ((fileset (dired-get-marked-files nil))
+        (inhibit-read-only t) (beg))
+    (sr-change-window)
+    (goto-char (point-max))
+    (setq beg (point))
+    (mapc (lambda (file)
+            (insert-char 32 2)
+            (setq file (dired-make-relative file default-directory)
+                  file (sr-chop ?/ file))
+            (insert-directory file sr-virtual-listing-switches))
+          fileset)
+    (sr-display-attributes beg (point-at-eol) sr-show-file-attributes)
+    (unwind-protect
+        (delete-region (point) (line-end-position))
+      (progn
+        (sr-change-window)
+        (dired-unmark-all-marks)))))
+
+(defun sr-ask (prompt target files function)
+  "Use FUNCTION to ask whether to do PROMPT on FILES with TARGET as destination."
+  (if (and files (listp files))
+      (let* ((len (length files))
+             (msg (if (< 1 len)
+                      (format "* [%d items]" len)
+                    (file-name-nondirectory (car files)))))
+        (if target
+            (setq msg (format "%s to %s" msg target)))
+        (funcall function (format "%s %s? " prompt msg)))))
+
+(defun sr-ask-overwrite (file-name)
+  "Ask whether to overwrite the given FILE-NAME."
+  (sr-y-n-or-a-p (format "File %s exists. OK to overwrite? " file-name)))
+
+(defun sr-y-n-or-a-p (prompt)
+  "Ask the user with PROMPT for an answer y/n/a ('a' stands for 'always').
+Returns t if the answer is y/Y, nil if the answer is n/N or the
+symbol `ALWAYS' if the answer is a/A."
+  (setq prompt (concat prompt "([y]es, [n]o or [a]lways)"))
+  (let ((resp -1))
+    (while (not (memq resp '(?y ?Y ?n ?N ?a ?A)))
+      (setq resp (read-event prompt))
+      (setq prompt "Please answer [y]es, [n]o or [a]lways "))
+    (if (>= resp 97)
+        (setq resp (- resp 32)))
+    (case resp
+      (?Y t)
+      (?A 'ALWAYS)
+      (t nil))))
+
+(defun sr-overlapping-paths-p (dir1 dir2)
+  "Return non-nil if directory DIR2 is located inside directory DIR1."
+  (when (and dir1 dir2)
+    (setq dir1 (expand-file-name (file-name-as-directory dir1))
+          dir2 (expand-file-name dir2))
+    (if (>= (length dir2) (length dir1))
+        (equal (substring dir2 0 (length dir1)) dir1)
+      nil)))
+
+(defun sr-list-of-contents (dir)
+  "Return the list of all files in DIR as a list of strings."
+  (sr-filter (function (lambda (x) (not (string-match "\\.\\.?/?$" x))))
+             (directory-files dir)))
+
+(defun sr-list-of-directories (dir)
+ "Return the list of directories in DIR as a list of strings.
+The list does not include the current directory and the parent directory."
+ (let ((result (sr-filter (function (lambda (x)
+                                      (file-directory-p (concat dir "/" x))))
+                          (sr-list-of-contents dir))))
+   (mapcar (lambda (x) (concat x "/")) result)))
+
+(defun sr-list-of-files (dir)
+  "Return the list of regular files in DIR as a list of strings.
+Broken links are *not* considered regular files."
+  (sr-filter
+   (function (lambda (x) (file-regular-p (concat dir "/" x))))
+   (sr-list-of-contents dir)))
+
+(defun sr-filter (p x)
+  "Return the elements of the list X that satisfy the predicate P."
+  (let ((res-list nil))
+    (while x
+      (if (apply p (list (car x)))
+          (setq res-list (cons (car x) res-list)))
+      (setq x (cdr x)))
+    (reverse res-list)))
+
+(defun sr-directory-name-proper (file-path)
+  "Return the proper name of the directory FILE-PATH, without initial path."
+  (if file-path
+      (let (
+            (file-path-1 (substring file-path 0 (- (length file-path) 1)))
+            (lastchar (substring file-path (- (length file-path) 1)))
+            )
+        (concat (file-name-nondirectory file-path-1) lastchar))))
+
+;;; ============================================================================
+;;; Directory and file comparison functions:
+
+(defun sr-compare-panes ()
+  "Compare the contents of Sunrise panes."
+  (interactive)
+  (let* ((file-alist1 (sr-files-attributes))
+         (other (sr-other 'buffer))
+         (file-alist2 (with-current-buffer other (sr-files-attributes)))
+         (progress
+          (sr-make-progress-reporter
+           "comparing" (+ (length file-alist1) (length file-alist2))))
+         (predicate `(prog1 ,(sr-ask-compare-panes-predicate)
+                            (sr-progress-reporter-update ',progress 1)))
+         (file-list1 (mapcar 'cadr (dired-file-set-difference
+                                    file-alist1 file-alist2 predicate)))
+         (file-list2 (mapcar 'cadr (dired-file-set-difference
+                                    file-alist2 file-alist1 predicate))))
+    (sr-md5 nil)
+    (dired-mark-if (member (dired-get-filename nil t) file-list1) nil)
+    (with-current-buffer other
+      (dired-mark-if (member (dired-get-filename nil t) file-list2) nil))
+    (message "Marked in pane1: %s files, in pane2: %s files"
+             (length file-list1)
+             (length file-list2))
+    (sit-for 0.2)))
+
+(defun sr-ask-compare-panes-predicate ()
+  "Prompt for the criterion to use for comparing the contents of the panes."
+  (let ((prompt "Compare by (d)ate, (s)ize, date_(a)nd_size, (n)ame \
+or (c)ontents? ")
+        (response -1))
+    (while (not (memq response '(?d ?D ?s ?S ?a ?A ?n ?N ?c ?C)))
+      (setq response (read-event prompt))
+      (setq prompt "Please select: Compare by (d)ate, (s)ize, date_(a)nd_size,\
+ (n)ame or (c)ontents? "))
+    (if (>= response 97)
+        (setq response (- response 32)))
+    (case response
+      (?D `(not (= mtime1 mtime2)))
+      (?S `(not (= size1 size2)))
+      (?N nil)
+      (?C `(not (string= (sr-md5 file1 t) (sr-md5 file2 t))))
+      (t `(or (not (= mtime1 mtime2)) (not (= size1 size2)))))))
+
+(defun sr-files-attributes ()
+  "Return a list of all file names and attributes in the current pane.
+The list has the same form as the one returned by
+`dired-files-attributes', but contains all the files currently
+displayed in VIRTUAL panes."
+  (delq
+   nil
+   (mapcar
+    (lambda (file-name)
+      (unless (member file-name '("." ".."))
+        (let ((full-file-name (expand-file-name file-name default-directory)))
+          (list file-name full-file-name (file-attributes full-file-name)))))
+    (sr-pane-files))))
+
+(defun sr-pane-files ()
+  "Return the list of files in the current pane.
+For VIRTUAL panes, returns the list of all files being currently
+displayed."
+  (delq
+   nil
+   (if (eq major-mode 'sr-virtual-mode)
+       (sr-buffer-files (current-buffer))
+     (directory-files default-directory))))
+
+(defvar sr-md5 '(nil) "Memoization cache for the sr-md5 function.")
+(defun sr-md5 (file-alist &optional memoize)
+  "Build and execute a shell command to calculate the MD5 checksum of a file.
+Second element of FILE-ALIST is the absolute path of the file. If
+MEMOIZE is non-nil, save the result into the `sr-md5' alist so it
+can be reused the next time this function is called with the same
+path. This cache can be cleared later calling `sr-md5' with nil
+as its first argument."
+  (if (null file-alist)
+      (setq sr-md5 '(nil))
+    (let* ((filename (cadr file-alist))
+           (md5-digest (cdr (assoc filename sr-md5)))
+           (md5-command))
+      (unless md5-digest
+        (setq md5-command
+              (replace-regexp-in-string
+               "%f" (format "\"%s\"" filename) sr-md5-shell-command))
+        (setq md5-digest (shell-command-to-string md5-command))
+        (if memoize
+            (push (cons filename md5-digest) sr-md5)))
+      md5-digest)))
+
+(defun sr-diff ()
+  "Run `diff' on the top two marked files in both panes."
+  (interactive)
+  (eval (sr-diff-form 'diff))
+  (sr-scrollable-viewer (get-buffer "*Diff*")))
+
+(defun sr-ediff ()
+  "Run `ediff' on the two top marked files in both panes."
+  (interactive)
+  (eval (sr-diff-form 'ediff)))
+
+(add-hook 'ediff-before-setup-windows-hook
+          (defun sr-ediff-before-setup-windows-function ()
+            (setq sr-ediff-on t)))
+
+(add-hook 'ediff-quit-hook
+          (defun sr-ediff-quit-function ()
+            (setq sr-ediff-on nil)
+            (when sr-running
+              (if (buffer-live-p sr-restore-buffer)
+                  (switch-to-buffer sr-restore-buffer))
+              (delete-other-windows)
+              (sr-setup-windows))))
+
+(defun sr-diff-form (fun)
+  "Return the appropriate form to evaluate for comparing files using FUN."
+  (let ((this (sr-pop-mark)) (other nil))
+    (unless this
+      (setq this (car (dired-get-marked-files t))))
+    (if (sr-equal-dirs default-directory sr-other-directory)
+        (setq other (sr-pop-mark))
+      (progn
+        (sr-change-window)
+        (setq other (sr-pop-mark))
+        (sr-change-window)
+        (setq other (or other
+                        (if (file-exists-p (concat sr-other-directory this))
+                            this
+                          (file-name-nondirectory this))))))
+    (setq this (concat default-directory this)
+          other (concat sr-other-directory other))
+    (list fun this other)))
+
+(defun sr-pop-mark ()
+  "Pop the first mark in the current Dired buffer."
+  (let ((result nil))
+    (condition-case description
+      (save-excursion
+        (goto-char (point-min))
+        (dired-next-marked-file 1)
+        (setq result (dired-get-filename t t))
+        (dired-unmark 1))
+      (error (message (cadr description))))
+    result))
+
+;;; ============================================================================
+;;; File search & analysis functions:
+
+(defun sr-process-kill ()
+  "Kill the process running in the current buffer (if any)."
+  (interactive)
+  (let ((proc (get-buffer-process (current-buffer))))
+    (and proc (eq (process-status proc) 'run)
+         (condition-case nil
+             (delete-process proc)
+           (error nil)))))
+
+(defvar sr-process-map (let ((map (make-sparse-keymap)))
+                         (set-keymap-parent map sr-virtual-mode-map)
+                         (define-key map "\C-c\C-k" 'sr-process-kill)
+                         map)
+  "Local map used in Sunrise panes during find and locate operations.")
+
+(defun sr-find-decorate-buffer (find-items)
+  "Provide details on `sr-find' execution in the current buffer.
+If the current find operation is done only in selected files and directories,
+modify the info line of the buffer to reflect this. Additionally, display an
+appropriate message in the minibuffer."
+  (rename-uniquely)
+  (when find-items
+    (let ((items-len (length find-items))
+          (max-items-len (window-width))
+          (inhibit-read-only t))
+      (goto-char (point-min))
+      (forward-line 1)
+      (when (re-search-forward "find \." nil t)
+        (if (> items-len max-items-len)
+            (setq find-items
+                  (concat (substring find-items 0 max-items-len) " ...")))
+        (replace-match (format "find %s" find-items)))))
+  (sr-beginning-of-buffer)
+  (sr-highlight)
+  (hl-line-mode 1)
+  (message (propertize "Sunrise find (C-c C-k to kill)"
+                       'face 'minibuffer-prompt)))
+
+(defun sr-find-apply (fun pattern)
+  "Helper function for functions `sr-find', `sr-find-name' and `sr-find-grep'."
+  (let* ((suffix (if (eq 'w32 window-system) " {} ;" " \\{\\} \\;"))
+         (find-ls-option
+          (cons
+           (concat "-exec ls -d " sr-virtual-listing-switches suffix)
+           "ls -ld"))
+         (sr-find-items (sr-quote-marked)) (dir))
+    (when sr-find-items
+      (if (not (y-or-n-p "Find in marked items only? "))
+          (setq sr-find-items nil)
+        (setq dir (directory-file-name (expand-file-name default-directory)))
+        (add-to-list 'file-name-handler-alist (cons dir 'sr-multifind-handler))))
+    (sr-save-aspect
+     (sr-alternate-buffer (apply fun (list default-directory pattern)))
+     (sr-virtual-mode)
+     (use-local-map sr-process-map)
+     (sr-keep-buffer))
+    (run-with-idle-timer 0.01 nil 'sr-find-decorate-buffer sr-find-items)))
+
+(defun sr-find (pattern)
+  "Run `find-dired' passing the current directory as first parameter."
+  (interactive "sRun find (with args): ")
+  (sr-find-apply 'find-dired pattern))
+
+(defun sr-find-name (pattern)
+  "Run `find-name-dired' passing the current directory as first parameter."
+  (interactive "sFind name pattern: ")
+  (sr-find-apply 'find-name-dired pattern))
+
+(defun sr-find-grep (pattern)
+  "Run `find-grep-dired' passing the current directory as first
+parameter. Called with prefix asks for additional grep options."
+  (interactive "sFind files containing pattern: ")
+  (let ((find-grep-options
+         (if current-prefix-arg
+             (concat find-grep-options
+                     " "
+                     (read-string "Additional Grep Options: "))
+         find-grep-options)))
+    (sr-find-apply 'find-grep-dired pattern)))
+
+(defadvice find-dired-sentinel
+  (after sr-advice-find-dired-sentinel (proc state))
+  "If the current find operation was launched inside the Sunrise
+Commander, create a new backup buffer on operation completion or
+abort."
+  (with-current-buffer (process-buffer proc)
+    (when (eq 'sr-virtual-mode major-mode)
+      (sr-backup-buffer))))
+(ad-activate 'find-dired-sentinel)
+
+(defadvice find-dired-filter
+  (around sr-advice-find-dired-filter (proc string))
+  "Disable the \"non-foolproof\" padding mechanism in `find-dired-filter' that
+breaks Dired when using ls options that omit some columns (like g or G). Defined
+by the Sunrise Commander."
+  (if (and (eq 'sr-virtual-mode major-mode)
+           (or (string-match "g" sr-virtual-listing-switches)
+               (string-match "G" sr-virtual-listing-switches)))
+      (let ((find-ls-option nil)) ad-do-it)
+    ad-do-it))
+(ad-activate 'find-dired-filter)
+
+(defun sr-multifind-handler (operation &rest args)
+  "Magic file name handler for manipulating the command executed by `find-dired'
+when the user requests to perform the find operation on all currently marked
+items (as opposed to the current default directory). Removes itself from the
+`inhibit-file-name-handlers' every time it's executed."
+  (let ((inhibit-file-name-handlers
+         (cons 'sr-multifind-handler
+               (and (eq inhibit-file-name-operation operation)
+                    inhibit-file-name-handlers)))
+        (inhibit-file-name-operation operation))
+    (when (eq operation 'shell-command)
+      (setq file-name-handler-alist
+            (rassq-delete-all 'sr-multifind-handler file-name-handler-alist))
+      (when sr-find-items
+        (setcar args (replace-regexp-in-string
+                      "find \." (format "find %s" sr-find-items) (car args)))))
+    (apply operation args)))
+
+(defun sr-flatten-branch (&optional mode)
+  "Display a flat view of the items contained in the current directory and all
+its subdirectories, sub-subdirectories and so on (recursively) in the active
+pane."
+  (interactive "cFlatten branch showing: (E)verything, (D)irectories,\
+ (N)on-directories or (F)iles only?")
+  (if (and mode (>= mode 97)) (setq mode (- mode 32)))
+  (case mode
+    (?E (sr-find-name "*"))
+    (?D (sr-find "-type d"))
+    (?N (sr-find "-not -type d"))
+    (?F (sr-find "-type f"))))
+
+(defun sr-prune-paths (regexp)
+  "Kill all lines (only the lines) in the current pane matching REGEXP."
+  (interactive "sPrune paths matching: ")
+  (save-excursion
+    (sr-beginning-of-buffer)
+    (while (if (string-match regexp (dired-get-filename t))
+               (dired-kill-line)
+             (dired-next-line 1)))))
+
+(defun sr-locate-filter (locate-buffer search-string)
+  "Return a filter function for the background `locate' process."
+  `(lambda (process output)
+     (let ((inhibit-read-only t)
+           (search-regexp ,(regexp-quote search-string))
+           (beg (point-max)))
+       (set-buffer ,locate-buffer)
+       (save-excursion
+         (mapc (lambda (x)
+                 (when (and (string-match search-regexp x) (file-exists-p x))
+                   (goto-char (point-max))
+                   (insert-char 32 2)
+                   (insert-directory x sr-virtual-listing-switches nil nil)))
+               (split-string output "[\r\n]" t))
+         (sr-display-attributes beg (point-at-eol) sr-show-file-attributes)))))
+
+(defun sr-locate-sentinel (locate-buffer)
+  "Return a sentinel function for the background locate process.
+Used to notify about the termination status of the process."
+  `(lambda (process status)
+     (let ((inhibit-read-only t))
+       (set-buffer ,locate-buffer)
+       (goto-char (point-max))
+       (insert "\n " locate-command " " status)
+       (forward-char -1)
+       (insert " at " (substring (current-time-string) 0 19))
+       (forward-char 1))
+     (sr-beginning-of-buffer)
+     (sr-highlight)
+     (hl-line-mode 1)))
+
+(defun sr-locate-prompt ()
+  "Display the message that appears when a locate process is launched."
+  (message (propertize "Sunrise locate (C-c C-k to kill)"
+                       'face 'minibuffer-prompt)))
+
+(defvar locate-command)
+(autoload 'locate-prompt-for-search-string "locate")
+(defun sr-locate (search-string &optional _filter _arg)
+  "Run locate asynchronously and display the results in Sunrise virtual mode."
+  (interactive
+   (list (locate-prompt-for-search-string) nil current-prefix-arg))
+  (let ((locate-buffer (create-file-buffer "*Sunrise Locate*"))
+        (process-connection-type nil)
+        (locate-process nil))
+    (sr-save-aspect
+     (sr-alternate-buffer (switch-to-buffer locate-buffer))
+     (cd "/")
+     (insert "  " default-directory ":")(newline)
+     (insert " Results of: " locate-command " " search-string)(newline)
+     (sr-virtual-mode)
+     (set-process-filter
+      (setq locate-process
+            (start-process "Async Locate" nil locate-command search-string))
+      (sr-locate-filter locate-buffer search-string))
+     (set-process-sentinel locate-process (sr-locate-sentinel locate-buffer))
+     (set-process-buffer locate-process locate-buffer)
+     (use-local-map sr-process-map)
+     (run-with-idle-timer 0.01 nil 'sr-locate-prompt))))
+
+(defun sr-fuzzy-narrow ()
+  "Interactively narrow contents of the current pane using fuzzy matching:
+  * press Delete or Backspace to revert the buffer to its previous state
+  * press Return, C-n or C-p to exit and accept the current narrowed state
+  * press Esc or C-g to abort the operation and revert the buffer
+  * use ! to prefix characters that should NOT appear beyond a given position.
+  Once narrowed and accepted, you can restore the original contents of the pane
+  by pressing g (`revert-buffer')."
+  (interactive)
+  (when sr-running
+    (sr-beginning-of-buffer)
+    (dired-change-marks ?* ?\t)
+    (let ((stack nil) (filter "") (regex "") (next-char nil) (inhibit-quit t))
+      (cl-labels ((read-next (f) (read-char (concat "Fuzzy narrow: " f))))
+        (setq next-char (read-next filter))
+        (sr-backup-buffer)
+        (while next-char
+          (case next-char
+            ((?\e ?\C-g) (setq next-char nil) (sr-revert-buffer))
+            (?\C-n (setq next-char nil) (sr-beginning-of-buffer))
+            (?\C-p (setq next-char nil) (sr-end-of-buffer))
+            ((?\n ?\r) (setq next-char nil))
+            ((?\b ?\d)
+             (revert-buffer)
+             (setq stack (cdr stack) filter (caar stack) regex (cdar stack))
+             (unless stack (setq next-char nil)))
+            (t
+             (setq filter (concat filter (char-to-string next-char)))
+             (if (not (eq next-char sr-fuzzy-negation-character))
+                 (setq next-char (char-to-string next-char)
+                       regex (if (string= "" regex) ".*" regex)
+                       regex (concat regex (regexp-quote next-char) ".*"))
+               (setq next-char (char-to-string (read-next filter))
+                     filter (concat filter next-char)
+                     regex (replace-regexp-in-string "\\.\\*\\'" "" regex)
+                     regex (concat regex "[^"(regexp-quote next-char)"]*")
+                     regex (replace-regexp-in-string "\\]\\*\\[\\^" "" regex)))
+             (setq stack (cons (cons filter regex) stack))))
+          (when next-char
+            (dired-mark-files-regexp (concat "^" regex "$"))
+            (dired-toggle-marks)
+            (dired-do-kill-lines)
+            (setq next-char (read-next filter)))))
+      (dired-change-marks ?\t ?*))))
+
+(defun sr-recent-files ()
+  "Display the history of recent files in Sunrise virtual mode."
+  (interactive)
+  (if (not (featurep 'recentf))
+      (error "ERROR: Feature recentf not available!"))
+
+  (sr-save-aspect
+   (let ((dired-actual-switches dired-listing-switches))
+     (sr-switch-to-clean-buffer "*Recent Files*")
+     (insert "Recently Visited Files: \n")
+     (dolist (file recentf-list)
+       (condition-case nil
+           (insert-directory file sr-virtual-listing-switches nil nil)
+         (error (ignore))))
+     (sr-virtual-mode)
+     (sr-keep-buffer))))
+
+(defun sr-recent-directories ()
+  "Display the history of directories recently visited in the current pane."
+  (interactive)
+  (sr-save-aspect
+   (let ((hist (cdr (assoc sr-selected-window sr-history-registry)))
+         (dired-actual-switches dired-listing-switches)
+         (pane-name (capitalize (symbol-name sr-selected-window)))
+         (switches (concat sr-virtual-listing-switches " -d")))
+     (sr-switch-to-clean-buffer (format "*%s Pane History*" pane-name))
+     (insert (concat "Recent Directories in " pane-name " Pane: \n"))
+     (dolist (dir hist)
+       (condition-case nil
+           (when dir
+             (setq dir (sr-chop ?/ (expand-file-name dir)))
+             (insert-directory dir switches nil nil))
+         (error (ignore))))
+     (sr-virtual-mode))))
+
+(defun sr-switch-to-clean-buffer (name)
+  (sr-alternate-buffer (switch-to-buffer name))
+  (erase-buffer))
+
+(defun sr-pure-virtual (&optional passive)
+  "Create a new empty buffer in Sunrise VIRTUAL mode.
+If the optional argument PASSIVE is non-nil, creates the virtual
+buffer in the passive pane."
+  (interactive "P")
+  (if passive
+      (progn
+        (sr-synchronize-panes)
+        (sr-in-other (sr-pure-virtual nil)))
+    (sr-save-aspect
+     (let* ((dir (directory-file-name (dired-current-directory)))
+            (buff (generate-new-buffer-name (buffer-name (current-buffer)))))
+       (sr-alternate-buffer (switch-to-buffer buff))
+       (goto-char (point-min))
+       (insert "  " dir ":")(newline)
+       (insert " Pure VIRTUAL buffer: ")(newline)
+       (sr-virtual-mode)
+       (sr-keep-buffer)))))
+
+(defun sr-dired-do-apply (dired-fun)
+  "Helper function for implementing `sr-do-query-replace-regexp' and Co."
+  (let ((buff (current-buffer)) (orig sr-restore-buffer))
+    (condition-case nil
+        (progn
+          (sr-quit)
+          (switch-to-buffer buff)
+          (call-interactively dired-fun)
+          (replace-buffer-in-windows buff)
+          (sr-bury-panes))
+      (quit
+       (when orig (switch-to-buffer orig))
+       (sunrise)))))
+
+(defun sr-do-query-replace-regexp ()
+  "Force Sunrise to quit before executing `dired-do-query-replace-regexp'."
+  (interactive)
+  (sr-dired-do-apply 'dired-do-query-replace-regexp))
+
+(defun sr-do-search ()
+  "Force Sunrise to quit before executing `dired-do-search'."
+  (interactive)
+  (sr-dired-do-apply 'dired-do-search))
+
+(defun sr-sticky-isearch-prompt ()
+  "Display the message that appears when a sticky search is launched."
+  (message (propertize "Sunrise sticky I-search (C-g to exit): "
+                       'face 'minibuffer-prompt)))
+
+(defvar sr-sticky-isearch-commands
+  '(nil
+    ("\C-o" . dired-omit-mode)
+    ("\M-a" . sr-beginning-of-buffer)
+    ("\M-e" . sr-end-of-buffer)
+    ("\C-v" . scroll-up-command)
+    ("\M-v" . (lambda () (interactive) (scroll-up-command '-)))
+    ("\C-g" . (lambda () (interactive) (save-excursion (isearch-abort))))
+  ) "Keybindings installed in `isearch-mode' during a sticky search.")
+
+(defun sr-sticky-isearch-remap-commands (&optional restore)
+  "Remap `isearch-mode-map' commands using `sr-sticky-isearch-commands'.
+Replace the bindings in our table with the previous ones from `isearch-mode-map'
+so we can restore them when the current sticky search operation finishes."
+  (when (eq restore (car sr-sticky-isearch-commands))
+    (setcar sr-sticky-isearch-commands (not restore))
+    (mapc (lambda (entry)
+            (let* ((binding (car entry))
+                   (old-command (lookup-key isearch-mode-map binding))
+                   (new-command (cdr entry)))
+              (define-key isearch-mode-map binding new-command)
+              (setcdr entry old-command)))
+          (cdr sr-sticky-isearch-commands))))
+
+(defun sr-sticky-isearch (&optional backward)
+  "Concatenate Isearch operations to allow fast file system navigation.
+Search continues until C-g is pressed (to abort) or Return is
+pressed on a regular file (to end the operation and visit that
+file)."
+  (set (make-local-variable 'search-nonincremental-instead) nil)
+  (add-hook 'isearch-mode-end-hook 'sr-sticky-post-isearch)
+  (sr-sticky-isearch-remap-commands)
+  (if backward
+      (isearch-backward nil t)
+    (isearch-forward nil t))
+  (run-hooks 'sr-refresh-hook)
+  (run-with-idle-timer 0.01 nil 'sr-sticky-isearch-prompt))
+
+(defun sr-sticky-isearch-forward ()
+  "Start a sticky forward search in the current pane."
+  (interactive)
+  (sr-sticky-isearch))
+
+(defun sr-sticky-isearch-backward ()
+  "Start a sticky backward search in the current pane."
+  (interactive)
+  (sr-sticky-isearch t))
+
+(defun sr-sticky-post-isearch ()
+  "`isearch-mode-end-hook' function for Sunrise sticky Isearch operations."
+  (and
+   (dired-get-filename nil t)
+   (let* ((filename (expand-file-name (dired-get-filename nil t)))
+          (is-dir (or (file-directory-p filename)
+                      (sr-avfs-dir filename)
+                      (sr-virtual-directory-p filename))))
+     (cond ((or isearch-mode-end-hook-quit (not is-dir))
+            (progn
+              (remove-hook 'isearch-mode-end-hook 'sr-sticky-post-isearch)
+              (kill-local-variable 'search-nonincremental-instead)
+              (sr-sticky-isearch-remap-commands t)
+              (isearch-done)
+              (if isearch-mode-end-hook-quit
+                  (run-hooks 'sr-refresh-hook)
+                (sr-find-file filename))))
+           (t
+            (progn
+              (sr-find-file filename)
+              (set (make-local-variable 'search-nonincremental-instead) nil)
+              (isearch-forward nil t)
+              (run-with-idle-timer 0.01 nil 'sr-sticky-isearch-prompt)))))))
+
+(defun sr-show-files-info (&optional deref-symlinks)
+  "Enhanced version of `dired-show-file-type' from dired‐aux.
+If at most one item is marked, print the filetype of the current
+item according to the \"file\" command, including its size in bytes.
+If more than one item is marked, print the total size in
+bytes (calculated recursively) of all marked items."
+  (interactive "P")
+  (message "Calculating total size of selection... (C-g to abort)")
+  (let* ((selection (dired-get-marked-files t))
+         (size (sr-size-format (sr-files-size selection)))
+         (items (length selection)) (label) (regex))
+    (if (>= 1 items)
+        (progn
+          (setq selection (car selection)
+                label (file-name-nondirectory selection)
+                regex (concat "^.*" label "[:;]")
+                label (concat label ":"))
+          (dired-show-file-type selection deref-symlinks)
+          (message
+           "%s (%s bytes)"
+           (replace-regexp-in-string regex label (current-message)) size))
+      (message "%s bytes in %d selected items" size items))
+    (sit-for 0.5)))
+
+(eval-when-compile
+  (defsubst sr-size-attr (file)
+    "Helper function for `sr-files-size'."
+    (float (or (nth 7 (file-attributes file)) 0))))
+
+(defun sr-files-size (files)
+  "Recursively calculate the total size of all FILES.
+FILES should be a list of paths."
+  (let ((result 0))
+    (mapc
+     (lambda (x) (setq result (+ x result)))
+     (mapcar (lambda (f) (cond ((string-match "\\.\\./?$" f) 0)
+                               ((string-match "\\./?$" f) (sr-size-attr f))
+                               ((file-symlink-p f) (sr-size-attr f))
+                               ((file-directory-p f) (sr-directory-size f))
+                               (t (float (sr-size-attr f)))))
+             files))
+    result))
+
+(defun sr-directory-size (directory)
+  "Recursively calculate the total size of the given DIRECTORY."
+  (sr-files-size (directory-files directory t nil t)))
+
+(defun sr-size-format (size)
+  "Return integer representation of SIZE (a float) as a string.
+Uses comma as the thousands separator."
+  (let* ((num (replace-regexp-in-string "\\..*$" "" (number-to-string size)))
+         (digits (reverse (split-string num "" t)))
+         result)
+    (dotimes (n (length digits))
+      (when (and (< 0 n) (zerop (% n 3)))
+        (setq result (concat "," result)))
+      (setq result (concat (pop digits) result)))
+    result))
+
+;;; ============================================================================
+;;; TI (Terminal Integration) and CLEX (Command Line EXpansion) functions:
+
+;;;###autoload
+(defun sr-term (&optional cd newterm program)
+  "Run terminal in a new buffer or switch to an existing one.
+If the optional argument CD is non-nil, directory is changed to
+the current one in the active pane. A non-nil NEWTERM argument
+forces the creation of a new terminal. If PROGRAM is provided
+and exists in `exec-path', then it will be used instead of the
+default `sr-terminal-program'."
+  (interactive)
+  (let ((aterm (car sr-ti-openterms)))
+    (if (and (null program)
+             (or (eq major-mode 'eshell-mode)
+                 (and (buffer-live-p aterm)
+                      (with-current-buffer aterm
+                        (eq major-mode 'eshell-mode)))))
+        (setq program "eshell")
+      (setq program (or program sr-terminal-program))))
+  (if (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+      (hl-line-mode 1))
+  (if (string= program "eshell")
+      (sr-term-eshell cd newterm)
+    (sr-term-extern cd newterm program)))
+
+;;;###autoload
+(defun sr-term-cd ()
+  "Run terminal in a new buffer or switch to an existing one.
+cd's to the current directory of the active pane."
+  (interactive)
+  (sr-term t))
+
+;;;###autoload
+(defun sr-term-cd-newterm ()
+  "Open a NEW terminal (don't switch to an existing one).
+cd's to the current directory of the active pane."
+  (interactive)
+  (sr-term t t))
+
+;;;###autoload
+(defun sr-term-cd-program (&optional program)
+  "Open a NEW terminal using PROGRAM as the shell."
+  (interactive "sShell program to use: ")
+  (sr-term t t program))
+
+(defmacro sr-term-excursion (cd newterm form &optional is-external)
+  "Take care of the common mechanics of launching or switching to a terminal.
+Helper macro."
+  `(let* ((start-buffer (current-buffer))
+          (new-term (or (null sr-ti-openterms) ,newterm))
+          (next-buffer (or (cadr (memq start-buffer sr-ti-openterms))
+                           (car sr-ti-openterms)))
+          (new-name) (is-line-mode))
+     (sr-select-viewer-window t)
+     (if (not new-term)
+         ;;don't switch anywhere else if we're in a term and we want only to cd:
+         (unless (and ,cd (memq (current-buffer) sr-ti-openterms))
+           (switch-to-buffer next-buffer))
+       (when next-buffer
+         (with-current-buffer next-buffer
+           (setq is-line-mode (and (boundp 'sr-term-line-minor-mode)
+                                   (symbol-value 'sr-term-line-minor-mode)))))
+       ,form
+       (if ,is-external (sr-term-char-mode))
+       (if is-line-mode (sr-term-line-mode))
+       (when (memq (current-buffer) sr-ti-openterms)
+         (rename-uniquely)
+         (setq new-name (buffer-name))
+         ,form)
+       (when new-name
+         (message "Sunrise: previous terminal renamed to %s" new-name))
+       (push (current-buffer) sr-ti-openterms))))
+
+(defun sr-term-line-mode ()
+  "Switch the current terminal to line mode.
+Apply additional Sunrise keybindings for terminal integration."
+  (interactive)
+  (term-line-mode)
+  (sr-term-line-minor-mode 1))
+
+(defun sr-term-char-mode ()
+  "Switch the current terminal to character mode.
+Bind C-j and C-k to Sunrise terminal integration commands."
+  (interactive)
+  (term-char-mode)
+  (sr-term-line-minor-mode 0)
+  (sr-term-char-minor-mode 1))
+
+(defun sr-term-extern (&optional cd newterm program)
+  "Implementation of `sr-term' for external terminal programs.
+See `sr-term' for a description of the arguments."
+  (let* ((program (if program (executable-find program)))
+         (program (or program sr-terminal-program))
+         (dir (expand-file-name (sr-choose-cd-target)))
+        (aterm (car sr-ti-openterms))
+        (cd (or cd (null sr-ti-openterms)))
+        (line-mode (if (buffer-live-p aterm)
+                       (with-current-buffer aterm (term-in-line-mode)))))
+    (sr-term-excursion cd newterm (term program) t)
+    (sr-term-char-mode)
+    (when (or line-mode (term-in-line-mode))
+      (sr-term-line-mode))
+    (when cd
+      (term-send-raw-string
+       (concat "cd " (shell-quote-wildcard-pattern dir) "\r")))))
+
+(defun sr-term-eshell (&optional cd newterm)
+  "Implementation of `sr-term' when using `eshell'."
+  (let ((dir (expand-file-name (sr-choose-cd-target)))
+        (cd (or cd (null sr-ti-openterms))))
+    (sr-term-excursion cd newterm (eshell))
+    (when cd
+      (insert (concat "cd " (shell-quote-wildcard-pattern dir)))
+      (eshell-send-input))
+    (sr-term-line-mode)))
+
+(defmacro sr-ti (form)
+  "Evaluate FORM in the context of the selected pane.
+Helper macro for implementing terminal integration in Sunrise."
+  `(if sr-running
+       (progn
+         (sr-select-window sr-selected-window)
+         (hl-line-unhighlight)
+         (unwind-protect
+             ,form
+           (when sr-running
+             (sr-select-viewer-window))))))
+
+(defun sr-ti-previous-line ()
+  "Move one line backward on active pane from the terminal window."
+  (interactive)
+  (sr-ti (forward-line -1)))
+
+(defun sr-ti-next-line ()
+  "Move one line forward on active pane from the terminal window."
+  (interactive)
+  (sr-ti (forward-line 1)))
+
+(defun sr-ti-select ()
+  "Run `dired-advertised-find-file' on active pane from the terminal window."
+  (interactive)
+  (sr-ti (sr-advertised-find-file)))
+
+(defun sr-ti-mark ()
+  "Run `dired-mark' on active pane from the terminal window."
+  (interactive)
+  (sr-ti (dired-mark 1)))
+
+(defun sr-ti-unmark ()
+  "Run `dired-unmark-backward' on active pane from the terminal window."
+  (interactive)
+  (sr-ti (dired-unmark-backward 1)))
+
+(defun sr-ti-prev-subdir (&optional count)
+  "Run `dired-prev-subdir' on active pane from the terminal window."
+  (interactive "P")
+  (let ((count (or count 1)))
+    (sr-ti (sr-dired-prev-subdir count))))
+
+(defun sr-ti-unmark-all-marks ()
+  "Remove all marks on active pane from the terminal window."
+  (interactive)
+  (sr-ti (dired-unmark-all-marks)))
+
+(defun sr-ti-change-window ()
+  "Switch focus to the currently active pane."
+  (interactive)
+  (sr-select-window sr-selected-window))
+
+(defun sr-ti-change-pane ()
+  "Change selection of active pane to passive one."
+  (interactive)
+  (sr-ti (sr-change-window)))
+
+(add-hook
+ 'kill-buffer-hook
+ (defun sr-ti-cleanup-openterms ()
+   "Remove the current buffer from the list of open terminals."
+   (setq sr-ti-openterms (delete (current-buffer) sr-ti-openterms))))
+
+(defun sr-ti-revert-buffer ()
+  "Refresh the currently active pane."
+  (interactive)
+  (let ((dir default-directory))
+    (if (not (sr-equal-dirs dir sr-this-directory))
+        (sr-ti (sr-goto-dir dir))
+      (sr-ti (sr-revert-buffer)))))
+
+(defun sr-ti-lock-panes ()
+  "Resize and lock the panes at standard position from the command line."
+  (interactive)
+  (sr-ti (sr-lock-panes)))
+
+(defun sr-ti-min-lock-panes ()
+  "Minimize the panes from the command line."
+  (interactive)
+  (sr-ti (sr-min-lock-panes)))
+
+(defun sr-ti-max-lock-panes ()
+  "Maximize the panes from the command line."
+  (interactive)
+  (sr-ti (sr-max-lock-panes)))
+
+(defmacro sr-clex (pane form)
+  "Evaluate FORM in the context of PANE.
+Helper macro for implementing command line expansion in Sunrise."
+  `(save-window-excursion
+     (setq pane (if (atom pane) pane (eval pane)))
+     (select-window (symbol-value (sr-symbol ,pane 'window)))
+     ,form))
+
+(defun sr-clex-marked (pane)
+  "Return a string containing the list of marked files in PANE."
+  (sr-clex
+   pane
+   (mapconcat 'shell-quote-wildcard-pattern (dired-get-marked-files) " ")))
+
+(defun sr-clex-file (pane)
+  "Return the file currently selected in PANE."
+  (sr-clex
+   pane
+   (concat (shell-quote-wildcard-pattern (dired-get-filename)) " ")))
+
+(defun sr-clex-marked-nodir (pane)
+  "Return a list of basenames of all the files currently marked in PANE."
+  (sr-clex
+   pane
+   (mapconcat 'shell-quote-wildcard-pattern
+              (dired-get-marked-files 'no-dir) " ")))
+
+(defun sr-clex-dir (pane)
+  "Return the current directory of the given pane."
+  (sr-clex
+   pane
+   (concat (shell-quote-wildcard-pattern default-directory) " ")))
+
+(defun sr-clex-start ()
+  "Start a new CLEX operation.
+Puts `sr-clex-commit' into local `after-change-functions'."
+  (interactive)
+  (if sr-clex-on
+      (progn
+        (setq sr-clex-on nil)
+        (delete-overlay sr-clex-hotchar-overlay))
+    (progn
+      (insert-char ?% 1)
+      (if sr-running
+          (progn
+            (add-hook 'after-change-functions 'sr-clex-commit nil t)
+            (setq sr-clex-on t)
+            (setq sr-clex-hotchar-overlay (make-overlay (point) (1- (point))))
+            (overlay-put sr-clex-hotchar-overlay 'face 'sr-clex-hotchar-face)
+            (message
+             "Sunrise: CLEX is now ON for keys: m f n d a p M F N D A P %%"))))))
+
+(defun sr-clex-commit (&optional _beg _end _range)
+  "Commit the current CLEX operation (if any).
+This function is added to the local `after-change-functions' list
+by `sr-clex-start'."
+  (interactive)
+  (if sr-clex-on
+      (progn
+        (setq sr-clex-on nil)
+        (delete-overlay sr-clex-hotchar-overlay)
+        (let* ((xchar (char-before))
+               (expansion (case xchar
+                            (?m (sr-clex-marked       'left))
+                            (?f (sr-clex-file         'left))
+                            (?n (sr-clex-marked-nodir 'left))
+                            (?d (sr-clex-dir          'left))
+                            (?M (sr-clex-marked       'right))
+                            (?F (sr-clex-file         'right))
+                            (?N (sr-clex-marked-nodir 'right))
+                            (?D (sr-clex-dir          'right))
+                            (?a (sr-clex-marked       '(sr-this)))
+                            (?A (sr-clex-dir          '(sr-this)))
+                            (?p (sr-clex-marked       '(sr-other)))
+                            (?P (sr-clex-dir          '(sr-other)))
+                            (t nil))))
+          (if expansion
+              (progn
+                (delete-char -2)
+                (insert expansion)))))))
+
+(define-minor-mode sr-term-char-minor-mode
+  "Sunrise Commander terminal add-on for character (raw) mode."
+  nil nil
+  '(("\C-c\C-j" . sr-term-line-mode)
+    ("\C-c\C-k" . sr-term-char-mode)
+    ("\C-c\t"   . sr-ti-change-window)
+    ("\C-ct"    . sr-term)
+    ("\C-cT"    . sr-term-cd)
+    ("\C-c\C-t" . sr-term-cd-newterm)
+    ("\C-c\M-t" . sr-term-cd-program)
+    ("\C-c;"    . sr-follow-viewer)
+    ("\C-c\\"   . sr-ti-lock-panes)
+    ("\C-c{"    . sr-ti-min-lock-panes)
+    ("\C-c}"    . sr-ti-max-lock-panes)))
+
+(define-minor-mode sr-term-line-minor-mode
+  "Sunrise Commander terminal add-on for line (cooked) mode."
+  nil nil
+  '(([M-up]        . sr-ti-previous-line)
+    ([A-up]        . sr-ti-previous-line)
+    ("\M-P"        . sr-ti-previous-line)
+    ([M-down]      . sr-ti-next-line)
+    ([A-down]      . sr-ti-next-line)
+    ("\M-N"        . sr-ti-next-line)
+    ("\M-\C-m"     . sr-ti-select)
+    ("\C-\M-j"     . sr-ti-select)
+    ([M-return]    . sr-ti-select)
+    ([S-M-return]  . sr-ti-select)
+    ("\M-M"        . sr-ti-mark)
+    ([M-backspace] . sr-ti-unmark)
+    ("\M-\d"       . sr-ti-unmark)
+    ("\M-J"        . sr-ti-prev-subdir)
+    ("\M-U"        . sr-ti-unmark-all-marks)
+    ([C-tab]       . sr-ti-change-window)
+    ([M-tab]       . sr-ti-change-pane)
+    ("\C-c\t"      . sr-ti-change-window)
+    ("\C-ct"       . sr-term)
+    ("\C-cT"       . sr-term-cd)
+    ("\C-c\C-t"    . sr-term-cd-newterm)
+    ("\C-c\M-t"    . sr-term-cd-program)
+    ("\C-c;"       . sr-follow-viewer)
+    ("\M-\S-g"     . sr-ti-revert-buffer)
+    ("%"           . sr-clex-start)
+    ("\t"          . term-dynamic-complete)
+    ("\C-c\\"      . sr-ti-lock-panes)
+    ("\C-c{"       . sr-ti-min-lock-panes)
+    ("\C-c}"       . sr-ti-max-lock-panes))
+  :group 'sunrise)
+
+(defadvice term-sentinel (around sr-advice-term-sentinel (proc msg) activate)
+  "Take care of killing Sunrise Commander terminal buffers on exit."
+  (if (and (or sr-term-char-minor-mode sr-term-line-minor-mode)
+           sr-terminal-kill-buffer-on-exit
+           (memq (process-status proc) '(signal exit)))
+      (let ((buffer (process-buffer proc)))
+        ad-do-it
+        (bury-buffer buffer)
+        (kill-buffer buffer))
+    ad-do-it))
+
+;;; ============================================================================
+;;; Desktop support:
+
+(defun sr-pure-virtual-p (&optional buffer)
+  "Return t if BUFFER (or the current buffer if nil) is purely virtual.
+Purely virtual means it is not attached to any directory or any
+file in the file system."
+  (with-current-buffer (if (bufferp buffer) buffer (current-buffer))
+    (not (or (eq 'sr-mode major-mode)
+             (and (eq 'sr-virtual-mode major-mode)
+                  buffer-file-truename
+                  (file-exists-p buffer-file-truename))))))
+
+(defun sr-desktop-save-buffer (desktop-dir)
+  "Return the additional data for saving a Sunrise buffer to a desktop file."
+  (unless (sr-pure-virtual-p)
+    (let* ((side (if (eq (current-buffer) sr-left-buffer) 'left 'right))
+           (sorting-order (get side 'sorting-order))
+           (sorting-reverse (get side 'sorting-reverse)))
+      (apply
+       'append
+       (delq nil
+             (list
+              (if (eq major-mode 'sr-virtual-mode)
+                  (list 'dirs buffer-file-truename)
+                (cons 'dirs (dired-desktop-buffer-misc-data desktop-dir)))
+              (cons side t)
+              (if sorting-order (cons 'sorting-order sorting-order))
+              (if sorting-reverse (cons 'sorting-reverse sorting-reverse))
+              (if (eq major-mode 'sr-virtual-mode) (cons 'virtual t))))
+       (mapcar (lambda (fun)
+                 (funcall fun desktop-dir))
+               sr-desktop-save-handlers)))))
+
+(defun sr-desktop-restore-buffer (desktop-buffer-file-name
+                                  desktop-buffer-name
+                                  desktop-buffer-misc)
+  "Restore a Sunrise (normal or VIRTUAL) buffer from its desktop file data."
+  (let* ((sr-running t)
+         (misc-data (cdr (assoc 'dirs desktop-buffer-misc)))
+         (is-virtual (assoc 'virtual desktop-buffer-misc))
+         (buffer
+          (if (not is-virtual)
+              (with-current-buffer
+                  (dired-restore-desktop-buffer desktop-buffer-file-name
+                                                desktop-buffer-name
+                                                misc-data)
+                (sr-mode)
+                (current-buffer))
+            (desktop-restore-file-buffer (car misc-data)
+                                         desktop-buffer-name
+                                         misc-data))))
+    (with-current-buffer buffer
+      (when is-virtual (set-visited-file-name nil t))
+      (mapc (lambda (side)
+              (when (cdr (assq side desktop-buffer-misc))
+                (set (sr-symbol side 'buffer) buffer)
+                (set (sr-symbol side 'directory) default-directory)
+                (sr-desktop-sort buffer side desktop-buffer-misc)))
+            '(left right))
+      (mapc (lambda (fun)
+              (funcall fun
+                       desktop-buffer-file-name
+                       desktop-buffer-name
+                       desktop-buffer-misc))
+            sr-desktop-restore-handlers))
+    buffer))
+
+(defun sr-desktop-sort (buffer side desktop-buffer-misc)
+  "Restore the sorting order in BUFFER to be displayed in SIDE.
+Use the data in DESKTOP-BUFFER-MISC to obtain all pertinent
+details."
+  (with-current-buffer buffer
+    (let ((sr-selected-window side)
+          (sorting-order (cdr (assoc 'sorting-order desktop-buffer-misc)))
+          (sorting-reverse (cdr (assoc 'sorting-reverse desktop-buffer-misc))))
+      (when sorting-order
+        (condition-case nil
+            (funcall (intern (format "sr-sort-by-%s" (downcase sorting-order))))
+          (error (ignore))))
+      (when sorting-reverse (sr-reverse-pane)))))
+
+(defun sr-reset-state ()
+  "Reset some environment variables that control the Sunrise behavior.
+Used for desktop support."
+  (setq sr-left-directory "~/" sr-right-directory "~/"
+        sr-this-directory "~/" sr-other-directory "~/")
+  (if sr-running (sr-quit))
+  nil)
+
+;; These register the previous functions in the desktop framework:
+(add-to-list 'desktop-buffer-mode-handlers
+             '(sr-mode . sr-desktop-restore-buffer))
+(add-to-list 'desktop-buffer-mode-handlers
+             '(sr-virtual-mode . sr-desktop-restore-buffer))
+
+;; This initializes (and sometimes starts) Sunrise after desktop restoration:
+(add-hook 'desktop-after-read-hook
+          (defun sr-desktop-after-read-function ()
+            (unless (assoc 'sr-running desktop-globals-to-clear)
+              (add-to-list 'desktop-globals-to-clear
+                           '(sr-running . (sr-reset-state))))
+            (if (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+                (sunrise))))
+
+;;; ============================================================================
+;;; Miscellaneous functions:
+
+(defun sr-buffer-files (buffer-or-name)
+  "Return the list of all file names currently displayed in the given buffer."
+  (with-current-buffer buffer-or-name
+    (save-excursion
+      (let ((result nil))
+        (sr-beginning-of-buffer)
+        (while (not (eobp))
+          (setq result (cons (dired-get-filename t t) result))
+          (forward-line 1))
+        (reverse result)))))
+
+(defun sr-keep-buffer (&optional side)
+  "Keep the currently displayed buffer in SIDE (left or right) window.
+Keeps it there even if it does not belong to the panel's history
+ring. If SIDE is nil, use the value of `sr-selected-window'
+instead. Useful for maintaining the contents of the pane during
+layout switching."
+  (let* ((side (or side sr-selected-window))
+         (window (symbol-value (sr-symbol side 'window))))
+    (set (sr-symbol side 'buffer) (window-buffer window))))
+
+(defun sr-scrollable-viewer (buffer)
+  "Set the `other-window-scroll-buffer' variable to BUFFER.
+Doing so allows to scroll the given buffer directly from the active pane."
+  (setq other-window-scroll-buffer buffer)
+  (if buffer
+      (message "QUICK VIEW: Press C-e/C-y to scroll, Space/M-Space to page, and C-u v (or C-u o) to dismiss")))
+
+(defun sr-describe-mode ()
+  "Call `describe-mode' and make the resulting buffer C-M-v scrollable."
+  (interactive)
+  (describe-mode)
+  (sr-scrollable-viewer (get-buffer "*Help*"))
+  (sr-select-window sr-selected-window))
+
+(defun sr-equal-dirs (dir1 dir2)
+  "Return non-nil if the two paths DIR1 and DIR2 represent the same directory."
+  (string= (expand-file-name (concat (directory-file-name dir1) "/"))
+           (expand-file-name (concat (directory-file-name dir2) "/"))))
+
+(defun sr-summary ()
+  "Summarize basic Sunrise commands and show recent Dired errors."
+  (interactive)
+  (dired-why)
+  (message "C-opy, R-ename, K-lone, D-elete, v-iew, e-X-ecute, Ff-ollow, \
+Jj-ump, q-uit, m-ark, u-nmark, h-elp"))
+
+(defun sr-restore-point-if-same-buffer ()
+  "Synchronize point position if the same buffer is displayed in both panes."
+  (let ((this-win)(other-win)(point))
+    (when (and (eq sr-left-buffer sr-right-buffer)
+               (window-live-p (setq other-win (sr-other 'window))))
+      (setq this-win (selected-window))
+      (setq point (point))
+      (select-window other-win)
+      (goto-char point)
+      (select-window this-win))))
+
+(defun sr-mark-toggle ()
+  "Toggle the mark on the current file or directory."
+  (interactive)
+  (when (dired-get-filename t t)
+    (if (eq ?  (char-after (line-beginning-position)))
+        (dired-mark 1)
+      (dired-unmark 1))))
+
+(defun sr-assoc-key (name alist test)
+  "Return the key in ALIST matched by NAME according to TEST."
+  (let (head (tail alist) found)
+    (while (and tail (not found))
+      (setq head (caar tail)
+            found (and (apply test (list head name)) head)
+            tail (cdr tail)))
+    found))
+
+(defun sr-quote-marked ()
+  "Return current pane's selected entries quoted and space-separated as a string."
+  (let (marked)
+    (condition-case err
+        (setq marked (dired-get-marked-files t nil nil t))
+      (error (unless (string= "No file on this line" (cadr err))
+               (signal (car err) (cdr err)))))
+    (unless (< (length marked) 2)
+      (if (eq t (car marked)) (setq marked (cdr marked)))
+      (format "\"%s\"" (mapconcat 'identity marked "\" \"")))))
+
+(defun sr-fix-listing-switches()
+  "Work around a bug in Dired that makes `dired-move-to-filename' misbehave
+when any of the options -p or -F is used with ls."
+  (mapc (lambda (sym)
+          (let ((val (replace-regexp-in-string "\\(?:^\\| \\)-[pF]*\\(?: \\|$\\)" " " (symbol-value sym))))
+            (while (string-match "\\(?:^\\| \\)-[^- ]*[pF]" val)
+              (setq val (replace-regexp-in-string "\\(\\(?:^\\| \\)-[^- ]*\\)[pF]\\([^ ]*\\)" "\\1\\2" val)))
+            (set sym val)))
+        '(sr-listing-switches sr-virtual-listing-switches))
+  (remove-hook 'sr-init-hook 'sr-fix-listing-switches))
+(add-hook 'sr-init-hook 'sr-fix-listing-switches)
+
+(defun sr-chop (char path)
+  "Remove all trailing instances of character CHAR from the string PATH."
+  (while (and (< 1 (length path))
+              (eq (string-to-char (substring path -1)) char))
+    (setq path (substring path 0 -1)))
+  path)
+
+;;; ============================================================================
+;;; Advice
+
+(defun sr-ad-enable (regexp &optional function)
+  "Put all or FUNCTION-specific advice matching REGEXP into effect.
+If provided, only update FUNCTION itself, otherwise all functions
+with advice matching REGEXP."
+  (if function
+      (progn (ad-enable-advice function 'any regexp)
+             (ad-activate function))
+    (ad-enable-regexp regexp)
+    (ad-activate-regexp regexp)))
+
+(defun sr-ad-disable (regexp &optional function)
+  "Stop all FUNCTION-specific advice matching REGEXP from taking effect.
+If provided, only update FUNCTION itself, otherwise all functions
+with advice matching REGEXP."
+  (if function
+      (progn (ad-disable-advice function 'any regexp)
+             (ad-update function))
+    (ad-disable-regexp regexp)
+    (ad-update-regexp regexp)))
+
+(defun sunrise-commander-unload-function ()
+  (sr-ad-disable "^sr-advice-"))
+
+;;; ============================================================================
+;;; Font-Lock colors & styles:
+
+(defmacro sr-rainbow (symbol spec regexp)
+  `(progn
+     (defface ,symbol '((t ,spec)) "Sunrise rainbow face" :group 'sunrise)
+     ,@(mapcar (lambda (m)
+                 `(font-lock-add-keywords ',m '((,regexp 1 ',symbol))))
+               '(sr-mode sr-virtual-mode))))
+
+(sr-rainbow sr-html-face              (:foreground "DarkOliveGreen")        "\\(^[^!].[^d].*\\.x?html?$\\)")
+(sr-rainbow sr-xml-face               (:foreground "DarkGreen")             "\\(^[^!].[^d].*\\.\\(xml\\|xsd\\|xslt?\\|wsdl\\)$\\)")
+(sr-rainbow sr-log-face               (:foreground "brown")                 "\\(^[^!].[^d].*\\.log$\\)")
+(sr-rainbow sr-compressed-face        (:foreground "magenta")               "\\(^[^!].[^d].*\\.\\(zip\\|bz2\\|t?[gx]z\\|[zZ]\\|[jwers]?ar\\|xpi\\|apk\\|xz\\)$\\)")
+(sr-rainbow sr-packaged-face          (:foreground "DarkMagenta")           "\\(^[^!].[^d].*\\.\\(deb\\|rpm\\)$\\)")
+(sr-rainbow sr-encrypted-face         (:foreground "DarkOrange1")           "\\(^[^!].[^d].*\\.\\(gpg\\|pgp\\)$\\)")
+
+(sr-rainbow sr-directory-face         (:inherit dired-directory :bold t)    "\\(^[^!].d.*\\)")
+(sr-rainbow sr-symlink-face           (:inherit dired-symlink :italic t)    "\\(^[^!].l.*[^/]$\\)")
+(sr-rainbow sr-symlink-directory-face (:inherit dired-directory :italic t)  "\\(^[^!].l.*/$\\)")
+(sr-rainbow sr-alt-marked-dir-face    (:foreground "DeepPink" :bold t)      "\\(^[^ *!D].d.*$\\)")
+(sr-rainbow sr-alt-marked-file-face   (:foreground "DeepPink")              "\\(^[^ *!D].[^d].*$\\)")
+(sr-rainbow sr-marked-dir-face        (:inherit dired-marked)               "\\(^[*!D].d.*$\\)")
+(sr-rainbow sr-marked-file-face       (:inherit dired-marked :bold nil)     "\\(^[*!D].[^d].*$\\)")
+(sr-rainbow sr-broken-link-face       (:inherit dired-warning :italic t)    "\\(^[!].l.*$\\)")
+
+(provide 'sunrise-commander)
+
+;;; sunrise-commander.el ends here
diff --git a/elisp/sunrise/sunrise-x-buttons.el b/elisp/sunrise/sunrise-x-buttons.el
new file mode 100644 (file)
index 0000000..eae9e09
--- /dev/null
@@ -0,0 +1,302 @@
+;;; sunrise-x-buttons.el --- mouse-clickable shortcut buttons for the Sunrise Commander File Manager -*- lexical-binding: t -*-
+
+;; Copyright (C) 2008-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 11 Jun 2008
+;; Version: 1
+;; RCS Version: $Rev: 444 $
+;; Keywords: sunrise commander, shortcut buttons
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-buttons.el
+;; Compatibility: GNU Emacs 22+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation,  either  version  3 of the License, or (at your option) any later
+;; version.
+;;
+;; This  program  is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR  A  PARTICULAR  PURPOSE.  See the GNU General Public License for more de-
+;; tails.
+
+;; You  should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Here is a small extension that may be of help to new users who want to get
+;; acquainted fast with the most frequent functions found in the Sunrise
+;; Commander and their keybindings. Once installed, it displays a panel with
+;; mouse clickable buttons that show some of the most useful actions performed
+;; by Sunrise and their respective bindings in the bottom window (a.k.a. viewer
+;; window here) every time the main panels are invoked. You can execute any of
+;; these functions by clicking the appropriate button, but the extension was
+;; conceived more as a simple cheat sheet (a very, very limited one, as you can
+;; easily learn by pressing the last button, labeled "More...") than as a real
+;; interface to Sunrise and Dired functions. Eventually, if you like this kind
+;; of interaction with the program you can add your own commands to the list and
+;; let this extension manage the creation and layout of the buttons for you.
+
+;; This extension was developed on GNU Emacs 23 on Linux, and tested on
+;; GNU Emacs 22 and 23 for Linux and on EmacsW32 (version 22) for Windows.
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'.
+
+;; 2) Add a (require 'sunrise-x-buttons) to your .emacs file, preferably right
+;; after (require 'sunrise-commander).
+
+;; 3) Evaluate the new expression, or reload your .emacs file, or restart Emacs.
+
+;; That's it - the next time you activate Sunrise you'll see a nice button panel
+;; in the viewer window.
+
+;;; Code:
+
+(require 'sunrise-commander)
+(require 'cus-edit)
+(eval-when-compile (require 'cl))
+
+(defvar sr-buttons-buffer-name "*Sunrise Buttons*"
+  "Name of the Sunrise buttons buffer")
+
+(defvar sr-buttons-command-adapter nil
+  "Function to use to execute button commands, or nil to do the default.")
+
+(defvar sr-buttons-list
+  '(
+    ("GotoDir([F2,]j,/)" 'sr-goto-dir                "Go to any directory in active pane")
+    ("View([F3,]v,o)"    'sr-quick-view              "View selected file or directory in this window")
+    ("Open([F4,]Enter)"  'sr-advertised-find-file    "Visit selected file or directory")
+    ("Copy([F5,]C)"      'sr-do-copy                 "Copy selected files to passive pane")
+    ("Rename([F6,]R)"    'sr-do-rename               "Move selected files to passive pane")
+    ("Clone(K)"          'sr-do-clone                "Clone selected files to passive pane")
+    ("NewDir([F7,]+)"    'dired-create-directory     "Create new directory in active pane")
+    ("Delete([F8,]D)"    'sr-do-delete               "Delete selected files from active pane")
+    nil
+    ("DirUp([C-PgUp,]J)" 'sr-dired-prev-subdir       "Go to parent directory in active pane")
+    ("DirBack(M-y)"      'sr-history-prev            "Go to previous directory in history")
+    ("DirFrwd(M-u)"      'sr-history-next            "Go to next directory in history")
+    ("HardLink(H)"       'sr-do-hardlink             "Make hard link of selected file in passive pane")
+    ("SymLink(S)"        'sr-do-symlink              "Make absolute symlink of selected entry in passive pane")
+    ("RelSymLink(Y)"     'sr-do-relsymlink           "Make relative symlink of selected entry in passive pane")
+    ("Hidden(C-o)"       'sr-omit-mode               "Hide/Show hidden files in active pane")
+    ("Attrs(C-Bksp)"     'sr-toggle-attributes       "Hide/Show file attributes in active pane")
+    nil
+    ("Other(Tab)"        'sr-change-window           "Switch to passive pane")
+    ("ClonePane(M-o)"    'sr-synchronize-panes       "Make both panes contain the same directory")
+    ("Swap(M-t)"         'sr-transpose-panes         "Transpose panes")
+    ("Refresh(g)"        'revert-buffer              "Rescan directory in active pane")
+    ("Align(C-cC-s)"     'sr-split-toggle            "Change panes alignment (vertical/horizontal/top)")
+    ("Sort(s)"           'sr-interactive-sort        "Sort interactively entries in active pane")
+    ("Mark([Ins,]m)"     'dired-mark                 "Mark selected entry in active pane")
+    ("Unmark(Bksp)"      'dired-unmark-backward      "Unmark last selected entry inactive pane")
+    nil
+    ("History(C-cC-d)"   'sr-recent-directories      "Display listing of recently visited directories")
+    ("Recent(C-cC-r)"    'sr-recent-files            "Display listing of recently visited files")
+    ("Restore(C-cC-c)"   'sr-buttons-restore-mode    "Dismiss VIRTUAL or WDired mode")
+    ("Find(C-cC-f)"      'sr-find                    "Find files and directories interactively")
+    ("FName(C-cC-n)"     'sr-find-name               "Find files and directories by name pattern")
+    ("FGrep(C-cC-g)"     'sr-find-grep               "Find files containing some expression")
+    ("Follow(;)"         'sr-follow-file             "Follow file (go to same directory as file)")
+    ("Locate(C-cC-l)"    'sr-locate                  "Find files and directories using locate database")
+    nil
+    ("Search(A)"         'sr-do-search               "Search for string/regexp in all marked entries")
+    ("Compare(C-M-=)"    'sr-compare-dirs            "Compare directories in panes")
+    ("Diff(=)"           'sr-diff                    "Compare selected entries using diff")
+    ("Ediff(C-=)"        'sr-ediff                   "Compare selected entries using ediff")
+    ("Store(C-c>)"       'sr-checkpoint-save         "Remember current position of panes as name")
+    ("Recall(C-c.)"      'sr-checkpoint-restore      "Set panes to a previously remembered position")
+    ("Home(M-a)"         'sr-beginning-of-buffer     "Go to first entry in active pane")
+    ("End(M-e)"          'sr-end-of-buffer           "Go to last entry in active pane")
+    nil
+    ("FindReplace(Q)"    'sr-do-query-replace-regexp "Find and replace in all selected entries")
+    ("Fuzzy(C-c/)"       'sr-fuzzy-narrow            "Narrow pane contents with fuzzy matching")
+    ("CmdLine(C-ct)"     'sr-term                    "Open Command line in this window")
+    ("WDired(C-xC-q)"    'sr-buttons-editable-pane   "Edit active pane using wdired")
+    ("SyncNav(C-cC-z)"   'sr-sync                    "Toggle on/off synchronized navigation mode")
+    ("LongLines(M-l)"    'sr-toggle-truncate-lines   "Truncate/Restore long lines in active pane")
+    ("More...(h)"        'sr-describe-mode           "More commands and keybindings")
+    ("Quit([F10,]q)"     'sr-quit                    "Dismiss Sunrise Commander")
+    )
+  "Sunrise button definitions.")
+
+(eval-and-compile
+  (unless (fboundp 'Custom-mode)
+    (defalias 'Custom-mode 'custom-mode)))
+
+(define-derived-mode sr-buttons-mode Custom-mode "Sunrise Buttons"
+  "Sunrise Commander Buttons panel mode."
+  :group 'sunrise
+  (set-keymap-parent sr-buttons-mode-map custom-mode-map)
+
+  (make-local-variable 'double-click-time)
+  (setq double-click-time nil)
+  (make-local-variable 'double-click-fuzz)
+  (setq double-click-fuzz 0)
+
+  (defun sr-buttons-click ()
+    "Handle all click events that take place in the buttons buffer."
+    (interactive)
+    (unwind-protect
+        (call-interactively 'widget-button-click)
+      (sr-select-window sr-selected-window)))
+
+  (mapc (lambda (x) (define-key sr-buttons-mode-map x 'sr-buttons-click))
+        '([down-mouse-1] [down-mouse-2] [down-mouse-3]))
+
+  (mapc (lambda (x) (define-key sr-buttons-mode-map x
+                      (lambda () (interactive)
+                        (sr-select-window sr-selected-window))))
+        '([(control tab)] "\C-c\t"
+          [mouse-1]             [mouse-2]             [mouse-3]
+          [drag-mouse-1]        [drag-mouse-2]        [drag-mouse-3]
+          [double-mouse-1]      [double-mouse-2]      [double-mouse-3]
+          [triple-mouse-1]      [triple-mouse-2]      [triple-mouse-3]
+          [double-drag-mouse-1] [double-drag-mouse-2] [double-drag-mouse-3]
+          [triple-drag-mouse-1] [triple-drag-mouse-2] [triple-drag-mouse-3]
+          [double-down-mouse-1] [double-down-mouse-2] [double-down-mouse-3]
+          [triple-down-mouse-1] [triple-down-mouse-2] [triple-down-mouse-3])))
+
+(add-hook 'sr-start-hook 'sr-buttons-display)
+(add-hook 'sr-quit-hook (defun sr-buttons-sr-quit-function ()
+                          (let ((buttons (get-buffer sr-buttons-buffer-name)))
+                            (if buttons (bury-buffer buttons)))))
+(add-hook 'kill-buffer-hook
+          (defun sr-buttons-kill-buffer-function ()
+            (if (and sr-running
+                     (eq (current-buffer) other-window-scroll-buffer))
+                (sr-buttons-display))))
+
+(defun sr-buttons-display ()
+  "Display the buttons buffer in the viewer window.
+If no buttons buffer exists yet, creates one."
+  (unless (and (boundp 'sr-popviewer-mode) (symbol-value 'sr-popviewer-mode))
+    (apply 'require '(cus-edit))
+    (sr-select-viewer-window t)
+    (cond ((buffer-live-p other-window-scroll-buffer) ;;<-- don't nuke quick views!
+           (switch-to-buffer other-window-scroll-buffer))
+          ((get-buffer "*terminal*")                  ;;<-- prefer terminals
+           (switch-to-buffer "*terminal*"))
+          (t
+           (switch-to-buffer sr-buttons-buffer-name)
+           (setq truncate-lines t)
+           (setq line-spacing 5)
+           (setq cursor-in-non-selected-windows nil)
+           (if (not (eq major-mode 'sr-buttons-mode))
+               (let ((line-spacing 2)
+                     (cursor-in-non-selected-windows nil))
+                 (sr-buttons-render)))))
+  (sr-select-window sr-selected-window)))
+
+(defun sr-buttons-render ()
+  "Populate current buffer with all widgets described in `sr-buttons-list'."
+  (sr-buttons-mode)
+  (let ((mc-keys-on (sr-buttons-mc-keys-p))
+        (maxlen (sr-buttons-maxtaglen)))
+    (mapc (lambda (x) (sr-buttons-build x mc-keys-on maxlen)) sr-buttons-list))
+  (sr-buttons-eol)
+  (goto-char (point-min)))
+
+(defun sr-buttons-build (spec mc-keys-on maxlen)
+  "Build and render a new widget in the buttons buffer.
+The first argument is an element of `sr-buttons-list' (list
+containing tag, action and hint), the second one is a flag that
+indicates whether mc style keybindings have been activated in
+Sunrise, and the last one is the length of the longest tag in the
+list."
+  (if (or (null spec)
+          (> (+ (current-column) maxlen) (- (window-width) (/ maxlen 2))))
+      (sr-buttons-eol)
+    (let ((tag (first spec))
+          (action (second spec))
+          (hint (third spec)))
+      (if mc-keys-on
+          (setq tag (replace-regexp-in-string "\\[\\|\\]" "" tag))
+        (setq tag (replace-regexp-in-string "\\[.*\\]" "" tag)))
+      (setq tag (sr-buttons-normalize-tag tag maxlen ? ))
+      (widget-create 'push-button :tag tag
+                                  :action (sr-buttons-action action)
+                                  :help-echo hint)
+      (insert-char ?  1)
+      (put-text-property
+       (1- (point)) (point) 'display (list 'space :width 0.15)))))
+
+(defun sr-buttons-eol ()
+  "Terminate the current row of buttons while building the buttons buffer.
+Centers it if necessary."
+  (let* ((gap (- (window-width) (current-column) 2))
+         (margin (/ gap 2)))
+    (if (> margin 0)
+        (save-excursion (beginning-of-line) (insert-char ?  margin)))
+    (unless (eq ?\n (char-before)) (insert "\n"))))
+
+(defun sr-buttons-mc-keys-p ()
+  "Determine whether mc-style keybindings have been activated in Sunrise."
+  (eq 'sr-goto-dir (cdr (assq 'f2 sr-mode-map))))
+
+(defun sr-buttons-maxtaglen ()
+  "Calculate the length of the longest tag in `sr-buttons-list'."
+  (let* ((regexp (if (sr-buttons-mc-keys-p) "\\[\\|\\]" "\\[.*\\]"))
+         (lenfun (lambda (x)
+                   (if x
+                       (length (replace-regexp-in-string regexp "" (car x)))
+                     0))))
+    (apply 'max (mapcar lenfun sr-buttons-list))))
+
+(defun sr-buttons-normalize-tag (tag total-length fill-char)
+  "Lengthen the given tag to TOTAL-LENGTH.
+Works by prepending and appending the appropriate number of fill
+characters, so the text appears approximately centered on its
+button."
+  (let* ((fill-length (- total-length (length tag)))
+         (before (/ fill-length 2))
+         (after (- fill-length before)))
+    (concat (make-string before fill-char)
+            tag
+            (make-string after fill-char))))
+
+(defun sr-buttons-action (action)
+  "Return a button command to perform ACTION inside the currently active pane."
+  `(lambda (&rest ignore)
+     (interactive)
+     (sr-select-window sr-selected-window)
+     (if sr-buttons-command-adapter
+         (run-with-timer 0.01 nil (funcall sr-buttons-command-adapter ,action))
+       (run-with-timer 0.01 nil (sr-buttons-do ,action)))))
+
+(defun sr-buttons-do (action)
+  "Execute ACTION interactively as response to the click of a button."
+  (hl-line-mode -1)
+  (call-interactively action)
+  (when (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+    (hl-line-mode 1)
+    (sr-graphical-highlight))
+  t)
+
+(defun sr-buttons-editable-pane ()
+  "Call `sr-editable-pane' and display an informative message.
+Used inside the Sunrise Buttons buffer."
+  (interactive)
+  (sr-editable-pane)
+  (message "Push [Restore] button or C-c C-c when done, ESC C-c C-c to cancel"))
+
+(defun sr-buttons-restore-mode ()
+  "Implement the [Restore] action in the Sunrise buttons panel."
+  (interactive)
+  (cond ((eq major-mode 'sr-virtual-mode) (sr-virtual-dismiss))
+        ((eq major-mode 'sr-tree-mode) (eval '(sr-tree-dismiss)))
+        ((string= mode-name "Editable Dired") (eval '(wdired-finish-edit)))
+        (t (message "Already in regular mode"))))
+
+(provide 'sunrise-x-buttons)
+
+;;;###autoload (eval-after-load 'sunrise-commander '(sr-extend-with 'sunrise-x-buttons))
+
+;;; sunrise-x-buttons.el ends here
diff --git a/elisp/sunrise/sunrise-x-checkpoints.el b/elisp/sunrise/sunrise-x-checkpoints.el
new file mode 100644 (file)
index 0000000..1d64482
--- /dev/null
@@ -0,0 +1,129 @@
+;;; sunrise-x-checkpoints.el --- checkpoint bookmarks for the Sunrise Commander File Manager -*- lexical-binding: t -*-
+
+;; Copyright (C) 2009-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 29 Dec 2009
+;; Version: 1
+;; RCS Version: $Rev: 440 $
+;; Keywords: sunrise commander, checkpoints, bookmarks
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-checkpoints.el
+;; Compatibility: GNU Emacs 23+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Beginning with version 4 of the Sunrise Commander, checkpoints were redefined
+;; to be a special form of bookmarks. Unfortunately, the differences between the
+;; bookmarks frameworks in Emacs 22 and Emacs 23 are so big that including this
+;; code directly in the sunrise-commander script would make it incompatible with
+;; Emacs 22. For this reason both versions of checkpoints are now provided as
+;; dynamically loaded extensions, so that you can decide which of them to use.
+;; To be sure, this is the version I intend to further develop, as it has a
+;; richer set of functions and integrates more nicely to the rest of Emacs. The
+;; other one is deprecated and will eventually disappear once Emacs 23+ becomes
+;; the "stable" release.
+
+;; This extension was written and tested on GNU Emacs 23 on Linux.
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'. (Optionally) compile
+;; it.
+
+;; 2) Enjoy ;-) -- Sunrise should pick the correct extension automatically. On
+;; Emacs 23 it will look for sunrise-x-checkpoints, while on Emacs 22 it'll try
+;; to load sunrise-x-old-checkpoints. Only if you *really* want to use the old
+;; extensions on Emacs 23 you may add a new (require 'sunrise-x-old-checkpoints)
+;; expression to your .emacs file somewhere after (require 'sunrise-commander).
+
+;;; Code:
+
+(require 'sunrise-commander)
+(require 'bookmark)
+(eval-when-compile (require 'cl))
+
+(defun sr-checkpoint-save (&optional _arg)
+  "Create a new checkpoint bookmark to save the location of both panes."
+  (interactive "p")
+  (sr-save-directories)
+  (let ((bookmark-make-record-function 'sr-make-checkpoint-record))
+    (call-interactively 'bookmark-set)))
+
+(defun sr-checkpoint-restore (&optional _arg)
+  "Call `bookmark-jump' interactively."
+  (interactive "p")
+  (call-interactively 'bookmark-jump)
+  (sr-history-push default-directory)
+  (sr-in-other (sr-history-push default-directory)))
+
+(defun sr-make-checkpoint-record ()
+  "Generate a the bookmark record for a new checkpoint."
+  `((filename . ,(format "Sunrise Checkpoint: %s | %s"
+                         sr-left-directory sr-right-directory))
+    (sr-directories . (,sr-left-directory ,sr-right-directory))
+    (handler . sr-checkpoint-handler)))
+
+(defun sr-checkpoint-handler (&optional bookmark)
+  "Handler for checkpoint bookmarks."
+  (or sr-running (sunrise))
+  (sr-select-window 'left)
+  (let ((dirs (cdr (assq 'sr-directories (cdr bookmark)))) (missing))
+    (mapc (lambda (x)
+            (if (file-directory-p x)
+                (sr-save-aspect (dired x) (sr-bookmark-jump))
+              (setq missing (cons sr-selected-window missing)))
+            (sr-change-window))
+          dirs)
+    (if missing (sr-checkpoint-relocate bookmark (reverse missing)))))
+
+(defun sr-checkpoint-relocate (bookmark &optional sides)
+  "Handle relocation of checkpoint bookmarks."
+  (interactive (list (bookmark-completing-read "Bookmark to relocate")))
+  (let* ((sides (or sides '(left right)))
+         (name (car bookmark))
+         (dirs (assq 'sr-directories (cdr bookmark)))
+         (relocs (mapcar
+                  (lambda (x)
+                    (read-directory-name
+                     (format "Relocate %s [%s] to: " name (symbol-name x))))
+                  sides))
+         (result (cond ((< 1 (length relocs)) relocs)
+                       ((eq 'right (car sides)) (list (cadr dirs) (car relocs)))
+                       (t (list (car relocs) (caddr dirs))))))
+    (setcdr dirs result)
+    (bookmark-set-filename
+     bookmark (apply 'format "Sunrise Checkpoint: %s | %s" result)))
+  (bookmark-save)
+  (sr-checkpoint-handler bookmark))
+
+(defadvice bookmark-relocate
+  (around sr-checkpoint-advice-bookmark-relocate (bookmark))
+  (let ((bmk (bookmark-get-bookmark bookmark)))
+    (if (assq 'sr-directories bmk)
+        (sr-checkpoint-relocate bmk)
+      ad-do-it)))
+(ad-activate 'bookmark-relocate)
+
+(defun sunrise-x-checkpoints-unload-function ()
+  (sr-ad-disable "^sr-checkpoint-"))
+
+(provide 'sunrise-x-checkpoints)
+
+;;; sunrise-x-checkpoints.el ends here
diff --git a/elisp/sunrise/sunrise-x-loop.el b/elisp/sunrise/sunrise-x-loop.el
new file mode 100644 (file)
index 0000000..b805323
--- /dev/null
@@ -0,0 +1,345 @@
+;;; sunrise-x-loop.el --- asynchronous execution of filesystem operations for the Sunrise Commander File Manager -*- lexical-binding: t -*-
+
+;; Copyright (C) 2008-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 27 Jun 2008
+;; Version: 3
+;; RCS Version: $Rev: 423 $
+;; Keywords: sunrise commander, background copy rename move
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-loop.el
+;; Compatibility: GNU Emacs 22+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This extension adds to the Sunrise Commander the capability of performing
+;; copy and rename operations in the background. It provides prefixable drop-in
+;; replacements for the `sr-do-copy' and `sr-do-rename' commands and uses them
+;; to redefine their bindings in the `sr-mode-map' keymap. When invoked the
+;; usual way (by pressing C or R), these new functions work exactly as the old
+;; ones, i.e. they simply pass the control flow to the logic already provided by
+;; Sunrise, but when prefixed (e.g. by pressing C-u C or C-u R) they launch a
+;; separate Elisp intepreter in the background, delegate to it the execution of
+;; all further operations and return immediately, so the Emacs UI remains fully
+;; responsive while any potentially long-running copy or move tasks can be let
+;; alone to eventually reach their completion in the background.
+
+;; After all requested actions have been performed, the background interpreter
+;; remains active for a short period of time (30 seconds by default, but it can
+;; be customized), after which it shuts down automatically.
+
+;; At any moment you can abort all tasks scheduled and under execution and force
+;; the background interpreter to shut down by invoking the `sr-loop-stop'
+;; command (M-x sr-loop-stop).
+
+;; If you need to debug something or are just curious about how this extension
+;; works, you can set the variable `sr-loop-debug' to t to have the interpreter
+;; launched in debug mode. In this mode all input and output of background
+;; operations are sent to a buffer named *SUNRISE-LOOP*. To return to normal
+;; mode set `sr-loop-debug' back to nil and use `sr-loop-stop' to kill the
+;; currently running interpreter.
+
+;; The extension disables itself and tries to do its best to keep out of the way
+;; when working with remote directories through FTP (e.g. when using ange-ftp),
+;; since in these cases the execution of file transfers in the background should
+;; be managed directly by the FTP client.
+
+;; It was written on GNU Emacs 23 on Linux, and tested on GNU Emacs 22 and 23
+;; for Linux and on EmacsW32 (version 22) for Windows.
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'.
+
+;; 2) Add a (require 'sunrise-x-loop) expression to your .emacs file somewhere
+;; after the (require 'sunrise-commander) one.
+
+;; 3) Evaluate the new expression, or reload your .emacs file, or restart Emacs.
+
+;; 4) The next time you need to copy of move any big files, just prefix the
+;; appropriate command with C-u.
+
+;; 5) Enjoy ;-)
+
+;; 6) You can use `unload-feature' to get rid of the provided functionality
+;; completely.
+
+;;; Code:
+
+(require 'sunrise-commander)
+
+(defcustom sr-loop-debug nil
+  "Activate debug mode in the Sunrise Loop extension.
+When set, the background elisp interpreter is launched in such a
+way that all background input and output are sent to a buffer
+named *SUNRISE LOOP* and automatic lifecycle management is
+disabled (i.e. you have to kill the interpreter manually using
+sr-loop-stop to get rid of it)."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-loop-timeout 30
+  "Number of seconds to wait while idle before shutting down the interpreter.
+After executing one or more operations in the background, the
+Sunrise Loop Elisp interpreter will be killed automatically after
+this amount of time."
+  :group 'sunrise)
+
+(defcustom sr-loop-use-popups t
+  "When non-nil, display pop‐up notification when execution queue is emptied."
+  :group 'sunrise
+  :type 'boolean)
+
+(defvar sr-loop-process nil)
+(defvar sr-loop-timer nil)
+(defvar sr-loop-scope nil)
+(defvar sr-loop-queue nil)
+
+(defun sr-loop-start ()
+  "Launch and initiate a new background Elisp interpreter.
+The new interpreter runs in batch mode and inherits all functions
+from the Sunrise Commander (sunrise-commander.el) and from this
+file."
+  (let ((process-connection-type nil)
+        (sr-main (symbol-file 'sr-mode))
+        (sr-loop (symbol-file 'sr-loop-cmd-loop))
+        (emacs (concat invocation-directory invocation-name)))
+    (setq sr-loop-process (start-process
+                         "Sunrise-Loop"
+                         (if sr-loop-debug "*SUNRISE-LOOP*" nil)
+                         emacs
+                         "-batch" "-q" "-no-site-file"
+                         "-l" sr-main "-l" sr-loop
+                         "-eval" "(sr-loop-cmd-loop)"))
+    (sr-loop-enqueue `(setq load-path (quote ,load-path)))
+    (sr-loop-enqueue '(require 'sunrise-commander))
+    (if sr-loop-debug
+        (sr-loop-enqueue '(setq sr-loop-debug t))
+      (set-process-filter sr-loop-process 'sr-loop-filter))
+    (setq sr-loop-queue nil)))
+
+(defun sr-loop-disable-timer ()
+  "Disable the automatic shutdown timer.
+This is done every time we send a new task to the background
+interpreter, lest it gets nuked before completing its queue."
+  (if sr-loop-timer
+      (progn
+        (cancel-timer sr-loop-timer)
+        (setq sr-loop-timer nil))))
+
+(defun sr-loop-enable-timer ()
+  "Enable the automatic shutdown timer.
+This is done every time we receive confirmation from the
+background interpreter that all the tasks delegated to it have
+been completed. Once this function is executed, if no new tasks
+are enqueued before `sr-loop-timeout' seconds, the interpreter is
+killed."
+  (sr-loop-disable-timer)
+  (setq sr-loop-timer (run-with-timer sr-loop-timeout nil 'sr-loop-stop)))
+
+(defun sr-loop-stop (&optional interrupt)
+  "Shut down the background Elisp interpreter and clean up after it."
+  (interactive "p")
+  (sr-loop-disable-timer)
+  (if sr-loop-queue
+      (if interrupt
+          (progn
+            (sr-loop-notify "Aborted. Some operations may remain unfinished.")
+            (setq sr-loop-queue nil))
+        (sr-loop-enable-timer)))
+  (unless sr-loop-queue
+    (delete-process sr-loop-process)
+    (setq sr-loop-process nil)))
+
+(defun sr-loop-notify (msg)
+  "Notify the user about an event."
+  (if (and window-system sr-loop-use-popups)
+      (x-popup-dialog t (list msg '("OK")) t)
+    (message (concat "[[" msg "]]"))))
+
+(defun sr-loop-filter (_process output)
+  "Process filter for the background interpreter."
+  (mapc (lambda (line)
+          (cond ((string-match "^\\[\\[\\*\\([^\]\*]+\\)\\*\\]\\]$" line)
+                 (sr-loop-notify (match-string 1 line)))
+
+                ((and (or (string-match "^\\[\\[" line)
+                          (string-match "^Sunrise Loop: " line))
+                      (< 0 (length line)))
+                 (message "%s" line))
+
+                ((eq ?^ (string-to-char line))
+                 (let ((command (substring line 1)))
+                   (when (string= command (car sr-loop-queue))
+                     (pop sr-loop-queue)
+                     (sr-loop-enable-timer)
+                     (unless sr-loop-queue
+                       (sr-loop-notify "Background job finished!")))))
+                (t nil)))
+        (split-string output "\n")))
+
+(defun sr-loop-enqueue (form)
+  "Delegate evaluation of FORM to the background interpreter.
+If no such interpreter is currently running, launches a new one."
+  (sr-loop-disable-timer)
+  (unless sr-loop-process
+    (sr-loop-start))
+  (let ((command (prin1-to-string form)))
+    (setq sr-loop-queue (append sr-loop-queue (list (md5 command))))
+    (process-send-string sr-loop-process command)
+    (process-send-string sr-loop-process "\n")))
+
+(defun sr-loop-cmd-loop ()
+  "Main execution loop for the background Elisp interpreter."
+  (sr-ad-disable "^sr-loop-")
+  (defun read-char nil ?y) ;; Always answer "yes" to any prompt
+  (let ((command) (signature))
+    (while t
+      (setq command (read))
+      (setq signature (md5 (prin1-to-string command)))
+      (condition-case description
+          (progn
+            (if sr-loop-debug
+                (message "%s" (concat "[[Executing in background: "
+                                      (prin1-to-string command) "]]")))
+            (eval command)
+            (message "[[Command successfully invoked in background]]"))
+        (error (message "%s" (concat "[[*ERROR IN BACKGROUND JOB: "
+                                     (prin1-to-string description) "*]]"))))
+        (message "^%s" signature))))
+
+(defun sr-loop-applicable-p ()
+  "Return non-nil if an operation is suitable for the background interpreter."
+  (and (null (string-match "^/ftp:" dired-directory))
+       (null (string-match "^/ftp:" sr-other-directory))))
+
+(defun sr-loop-do-copy (&optional arg)
+  "Drop-in prefixable replacement for the `sr-do-copy' command.
+When invoked with a prefix argument, sets a flag that is used
+later by advice to decide whether to delegate further copy
+operations to the background interpreter."
+  (interactive "P")
+  (if (and arg (sr-loop-applicable-p))
+      (let ((sr-loop-scope t))
+        (sr-do-copy))
+    (sr-do-copy)))
+
+(defun sr-loop-do-clone (&optional arg)
+  "Drop-in prefixable replacement for the `sr-do-clone' command.
+When invoked with a prefix argument, sets a flag that is used
+later by advice to decide whether to delegate further copy
+operations to the background interpreter."
+  (interactive "P")
+  (if (and arg (sr-loop-applicable-p))
+      (let ((sr-loop-scope t))
+        (call-interactively 'sr-do-clone))
+    (call-interactively 'sr-do-clone)))
+
+(defun sr-loop-do-rename (&optional arg)
+  "Drop-in  prefixable  replacement  for  the `sr-do-rename' command.
+When invoked with a prefix argument, sets a flag that is used
+later by advice to decide whether to delegate further rename
+operations to the background interpreter."
+  (interactive "P")
+  (if (and arg (sr-loop-applicable-p))
+      (let ((sr-loop-scope t))
+        (sr-do-rename))
+    (sr-do-rename)))
+
+(defadvice sr-progress-prompt (around sr-loop-advice-sr-progress-prompt
+                                      activate)
+  "Display \"Sunrise Loop\" instead of \"Sunrise\" in the prompt."
+  (setq ad-return-value
+        (concat (if sr-loop-scope "Sunrise Loop: " "Sunrise: ")
+                (ad-get-arg 0)
+                "...")))
+
+(defadvice y-or-n-p (before sr-loop-advice-y-or-n-p activate)
+  "Modify all confirmation request messages inside a loop scope."
+  (when sr-loop-scope
+    (setq (ad-get-arg 0)
+          (replace-regexp-in-string
+           "\?" " in the background? (overwrites ALWAYS!)" (ad-get-arg 0)))))
+
+(defadvice dired-mark-read-file-name
+  (before sr-loop-advice-dired-mark-read-file-name
+          (prompt dir op-symbol arg files &optional default)
+          activate)
+  "Modify all queries from Dired inside a loop scope."
+  (if sr-loop-scope
+      (setq prompt (replace-regexp-in-string
+                    "^\\([^ ]+\\) ?\\(.*\\)"
+                    "\\1 (in background - overwrites ALWAYS!) \\2" prompt))))
+
+(defadvice dired-create-files
+  (around sr-loop-advice-dired-create-files
+          (file-creator operation fn-list name-constructor
+                        &optional marker-char)
+          activate)
+  "Delegate to the background interpreter all copy and rename operations
+triggered by `dired-do-copy' inside a loop scope."
+  (if sr-loop-scope
+      (with-no-warnings
+        (sr-loop-enqueue
+         `(let ((target ,target))       ; cf. `dired-do-create-files'
+            (dired-create-files (function ,file-creator)
+                                ,operation
+                                (quote ,fn-list)
+                                ,name-constructor nil))))
+    ad-do-it))
+
+(defadvice sr-clone-files
+  (around sr-loop-advice-sr-clone-files
+          (file-path-list target-dir clone-op progress &optional do-overwrite)
+          activate)
+  "Delegate to the background interpreter all copy operations
+triggered by `sr-do-copy' inside a loop scope."
+  (if sr-loop-scope
+      (sr-loop-enqueue
+       `(sr-clone-files
+         (quote ,file-path-list) ,target-dir #',clone-op ',progress 'ALWAYS))
+    ad-do-it))
+
+(defadvice sr-move-files
+  (around sr-loop-advice-sr-move-files
+          (file-path-list target-dir progress &optional do-overwrite)
+          activate)
+  "Delegate to the background interpreter all rename operations
+triggered by `sr-do-rename' inside a loop scope."
+  (if sr-loop-scope
+      (sr-loop-enqueue
+       `(sr-move-files (quote ,file-path-list) ,target-dir ',progress 'ALWAYS))
+    ad-do-it))
+
+(define-key sr-mode-map "C" 'sr-loop-do-copy)
+(define-key sr-mode-map "K" 'sr-loop-do-clone)
+(define-key sr-mode-map "R" 'sr-loop-do-rename)
+
+(defun sunrise-x-loop-unload-function ()
+  (sr-ad-disable "^sr-loop-")
+  (define-key sr-mode-map "C" 'sr-do-copy)
+  (define-key sr-mode-map "K" 'sr-do-clone)
+  (define-key sr-mode-map "R" 'sr-do-rename))
+
+(provide 'sunrise-x-loop)
+
+;;;###autoload (eval-after-load 'sunrise-commander '(sr-extend-with 'sunrise-x-loop))
+
+;;; sunrise-x-loop.el ends here
diff --git a/elisp/sunrise/sunrise-x-mirror.el b/elisp/sunrise/sunrise-x-mirror.el
new file mode 100644 (file)
index 0000000..8c4c277
--- /dev/null
@@ -0,0 +1,569 @@
+;;; sunrise-x-mirror.el --- full read/write access to compressed archives for the Sunrise Commander File Manager -*- lexical-binding: t -*-
+
+;; Copyright (C) 2008-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 4 May 2008
+;; Version: 2
+;; RCS Version: $Rev: 423 $
+;; Keywords: sunrise commander, archives read/write
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-mirror.el
+;; Compatibility: GNU Emacs 22+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This is an extension for the Sunrise Commander file manager (for more details
+;; visit http://www.emacswiki.org/emacs/Sunrise_Commander), that allows browsing
+;; compressed archives in full read-write mode. Sunrise does offer means for
+;; transparent browsing archives (using AVFS), but they just provide read-only
+;; navigation -- if you want to edit a file inside the virtual filesystem, copy,
+;; remove, or rename anything, you still have to uncompress the archive, do the
+;; stuff and compress it back yourself.
+
+;; It uses one or unionfs-fuse or funionfs  to create a writeable overlay on top
+;; of the read-only filesystem provided by AVFS. You can freely add, remove or
+;; modify anything inside the resulting union filesystem (a.k.a. the "mirror
+;; area"), and then commit all modifications (or not) to the original archive
+;; with a single keystroke. There is no preliminary uncompressing of the archive
+;; and nothing happens if you don't make changes (or if you don't commit them).
+;; On commit, the contents of the union fs are compressed to create an updated
+;; archive to replace the original one (optionally after making a backup copy of
+;; it, just in case).
+
+;; Navigating outside a mirror area will automatically close it, so if you do it
+;; you may be asked whether to commit or not to the archive all your changes. In
+;; nested archives (e.g. a jar inside a zip inside a tgz), partial modifications
+;; are committed silently on the fly if moving out from a modified archive to
+;; one that contains it. Only if you leave the topmost mirror area you will be
+;; asked for confirmation whether to modify the resulting archive.
+
+;; Be warned, though, that this method may be impractical for very large or very
+;; deeply nested archives with strong compression, since the uncompressing
+;; happens in the final stage and requires multiple access operations through
+;; AVFS. What this means is that probably you'll have to wait a looooong time if
+;; you try to commit changes to a tar.bz2 file with several hundreds of
+;; megabytes in size, or under five or six other layers of strong compression.
+
+;; For this extension to work you must have:
+
+;; 1) FUSE + AVFS support in your Sunrise Commander. If you can navigate (read-
+;; only) inside compressed archives you already have this.
+
+;; 2) One of unionfs-fuse or funionfs. Debian squeeze (stable) offers a package
+;; for the first, which is currently the recommended implementation.
+
+;; 3) Programs required for repacking archives -- at least zip and tar.
+
+;; 4) Your AVFS mount point (and the value of variable `sr-avfs-root') must be
+;; in a directory where you have writing access.
+
+;; All this means is that most probably this extension will work out-of-the-box
+;; on Linux (or MacOS, or other unices), but you'll have a hard time to make it
+;; work on Windows. It was written on GNU Emacs 23 on Linux and tested on GNU
+;; Emacs 22 and 23 for Linux.
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'.
+
+;; 2) Add a (require 'sunrise-x-mirror) to your .emacs file, anywhere after the
+;; (require 'sunrise-commander) sexp.
+
+;; 3) Evaluate the new expression, or reload your .emacs file, or restart Emacs.
+
+;; 4) Customize the variable `sr-mirror-unionfs-impl' and select your preferred
+;; unionfs implementation (either unionfs-fuse or funionfs).
+
+;; 5) Run the Sunrise Commander (M-x sunrise), select (or navigate inside) any
+;; compressed directory in the active pane and press C-c C-b. This will
+;; automatically take you to the mirror area for the selected archive. You can
+;; make any modifications you want to the contents of the archive, or navigate
+;; inside directories or other compressed archives inside it. When you're done,
+;; press again C-c C-b anywhere inside the mirror area, or simply navigate out
+;; of it. If there are any changes to commit (*and* if you confirm) the original
+;; archive will be replaced with a new one with the contents of the mirror area
+;; you've just been working on. If you don't change the defaults, the original
+;; will be renamed with a ".bak" extension added.
+
+;; 6) You can add support for new archive formats by adding new entries to the
+;; `sr-mirror-pack-commands-alist' custom variable, which contains a regular
+;; expression to match against the name of the archive and a string containing
+;; the shell command to execute for packing back the mirror area into a
+;; compressed archive.
+
+;; 7) Once you've gained enough confidence using this extension you can reset
+;; the `sr-mirror-keep-backups' flag to get rid of all the backup copies
+;; produced by it.
+
+;; 8) Enjoy ;)
+
+;;; Code:
+
+(require 'sunrise-commander)
+(eval-when-compile (require 'cl))
+
+(defcustom sr-mirror-keep-backups t
+  "If non-nil, keep backup files when committing changes to read-only archives."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-mirror-pack-commands-alist
+  '(
+    ("\\.\\(?:zip\\|xpi\\|apk\\)$" . "zip -r   %f *")
+    ("\\.[jwesh]ar$"               . "zip -r   %f *")
+    ("\\.tar$"                     . "tar cvf  %f *")
+    ("\\.\\(?:tar\\.gz\\|tgz\\)$"  . "tar cvzf %f *")
+    ("\\.tar\\.bz2$"               . "tar cvjf %f *")
+    ("\\.\\(?:tar\\.xz\\|txz\\)$"  . "tar cvJf %f *")
+   )
+  "List of shell commands to repack particular archive contents.
+Used when repacking contents from a mirror area into a compressed
+archive of the appropriate type. Use %f as a placeholder for the
+name of the resulting archive. If no repacking command has been
+registered here for a file (usually a file extension), Sunrise
+will refuse to create a mirror area for it even if it is normally
+browseable through AVFS."
+  :group 'sunrise
+  :type 'alist)
+
+(defcustom sr-mirror-unionfs-impl 'unionfs-fuse
+  "Implementation of unionfs to use for creating mirror areas."
+  :group 'sunrise
+  :type '(choice (const :tag "unionfs-fuse" unionfs-fuse)
+         (const :tag "funionfs" funionfs)))
+
+(defface sr-mirror-path-face
+  '((t (:background "blue" :foreground "yellow" :bold t :height 120)))
+  "Face of the directory path inside mirror areas."
+  :group 'sunrise)
+
+(defvar sr-mirror-home nil
+  "Root directory of all mirror areas.
+Set automatically by the function `sr-mirror-enable' and reset by
+`sr-mirror-disable' to keep the mirror home path, as well as to
+indicate mirroring support is on/off. Do not mess with it
+directly - if you need to change the name of your mirror home
+dir, modify `sr-mirror-enable'.")
+
+(defvar sr-mirror-divert-goto-dir t
+  "Internal variable used to avoid infinite recursion.
+Used when diverting `sr-goto-dir' calls to `sr-mirror-goto-dir'.
+Do not touch, or else.")
+
+(if (boundp 'sr-mode-map)
+    (define-key sr-mode-map "\C-c\C-b" 'sr-mirror-toggle))
+
+(defun sr-mirror-enable ()
+  "Enable Sunrise mirror support.
+Sets the variable `sr-mirror-home' to a non-nil value and
+activates all advice necessary for mirror operations. This method
+is called every time a new mirror area is created."
+  (unless sr-mirror-home
+    (setq sr-mirror-home (concat sr-avfs-root "#mirror#/"))
+    (ad-activate 'make-directory)
+    (ad-activate 'save-buffer)
+    (ad-activate 'sr-goto-dir)))
+
+(defun sr-mirror-disable ()
+  "Disable Sunrise mirror support.
+Resets `sr-mirror-home' and deactivates all advice used in mirror
+operations. This method is called after the last mirror area in
+the current mirror home is closed."
+  (when sr-mirror-home
+    (setq sr-mirror-home nil)
+    (ad-deactivate 'make-directory)
+    (ad-deactivate 'save-buffer)
+    (ad-deactivate 'sr-goto-dir)))
+
+(defun sr-mirror-open ()
+  "Set up a mirror area in the current pane.
+Uses unionfs-fuse to create a writeable filesystem overlay over the AVFS virtual
+filesystem of the selected compressed archive and displays it in the current
+pane. The result is a mirror of the contents of the original archive that is
+fully writeable."
+  (interactive)
+  (let ((path (or (dired-get-filename nil t)
+                  (concat (expand-file-name (dired-current-directory)) "/.")))
+        (sr-mirror-divert-goto-dir nil)
+        (sr-avfs-root (expand-file-name sr-avfs-root))
+        fname vpaths)
+    (if (sr-overlapping-paths-p sr-avfs-root path)
+        (unless (and sr-mirror-home (sr-overlapping-paths-p sr-mirror-home path))
+          (setq path (substring path (length sr-avfs-root))
+                vpaths (split-string path "#[^/]*/")
+                path (car vpaths)
+                vpaths (cdr vpaths))))
+    (setq fname (file-name-nondirectory path))
+    (if (null (assoc-default fname sr-mirror-pack-commands-alist 'string-match))
+        (error (concat "Sunrise: sorry, no packer was registered for " fname)))
+    (sr-mirror-enable)
+    (unless (file-exists-p sr-mirror-home)
+      (make-directory sr-mirror-home))
+    (if vpaths
+        (mapc (lambda (x)
+                (let ((sr-mirror-divert-goto-dir nil))
+                  (sr-goto-dir (sr-mirror-mount path))
+                  (sr-follow-file x)
+                  (setq path (dired-get-filename))))
+              vpaths)
+      (sr-goto-dir (sr-mirror-mount path)))
+    (sr-graphical-highlight 'sr-mirror-path-face)
+    (add-hook 'kill-buffer-hook 'sr-mirror-on-kill-buffer)
+    t ))
+
+(defun sr-mirror-mount (path)
+  "Create and mount (if necessary) all the directories needed to mirror PATH.
+PATH identifies the compressed archive. Returns the path to the
+corresponding mirror area."
+  (let* ((base (sr-mirror-mangle path))
+         (virtual (sr-mirror-full-demangle path))
+         (mirror (concat sr-mirror-home base))
+         (overlay (concat sr-mirror-home "." base))
+         (command
+          (case sr-mirror-unionfs-impl
+            (unionfs-fuse
+             (concat "cd ~; unionfs-fuse -o cow,kernel_cache -o allow_other "
+                     overlay "=RW:" virtual "=RO " mirror))
+
+            (funionfs
+             (concat "cd ~; funionfs " overlay " " mirror
+                     " -o dirs=" virtual "=ro")))))
+    (if (null virtual)
+        (error (concat "Sunrise: sorry, don't know how to mirror " path)))
+    (unless (file-directory-p mirror)
+      (make-directory mirror)
+      (make-directory overlay)
+      (shell-command-to-string command))
+    mirror))
+
+(defun sr-mirror-close (&optional do-commit local-commit moving)
+  "Destroy the current mirror area.
+Unmounts and deletes the directories it was built upon. Tries to
+automatically repack the mirror and substitute the original archive
+with a new one containing the modifications made to the mirror.
+
+If optional argument DO-COMMIT is set, then all changes made to the
+mirror are unconditionally committed to the archive. If
+LOCAL-COMMIT is set, then the commit is considered local (changes
+effect a mirror nested inside another mirror). MOVING means that
+this operation was triggered by the user moving outside of the
+current mirror area (the current buffer will be killed soon)."
+  (interactive)
+  (unless sr-mirror-home
+    (error (concat "Sunrise: sorry, can't mirror " (dired-get-filename))))
+
+  (let ((here (dired-current-directory))
+        (sr-mirror-divert-goto-dir nil)
+        (pos) (mirror) (overlay) (vroot) (vpath) (committed))
+
+    (unless (sr-overlapping-paths-p sr-mirror-home here)
+      (error (concat "Sunrise: sorry, that's not a mirror area: " here)))
+
+    (setq pos (string-match "\\(?:/\\|$\\)" here (length sr-mirror-home))
+          mirror (substring here (length sr-mirror-home) pos)
+          overlay (concat "." mirror )
+          vpath (substring here (1+ pos))
+          do-commit (and (sr-mirror-files (concat sr-mirror-home overlay))
+                         (or do-commit
+                             (y-or-n-p "Sunrise: commit changes in mirror? "))))
+
+    (unless local-commit
+      (sr-unhighlight 'sr-mirror-path-face))
+
+    (remove-hook 'kill-buffer-hook 'sr-mirror-on-kill-buffer)
+    (sr-follow-file (sr-mirror-demangle mirror))
+    (setq vroot (dired-get-filename 'no-dir))
+
+    (if do-commit (setq committed (sr-mirror-commit mirror)))
+    (sr-mirror-unmount mirror overlay)
+
+    (unless local-commit
+      (if (sr-overlapping-paths-p sr-mirror-home (dired-current-directory))
+          (sr-mirror-close committed))
+      (unless moving
+        (sr-find-file (expand-file-name (concat default-directory vroot)))
+        (if (< 0 (length vpath)) (sr-goto-dir vpath)))))
+
+  (sr-highlight)
+  (if (and sr-mirror-home
+           (null (directory-files sr-mirror-home nil "^[^.]")))
+      (sr-mirror-disable))
+  t)
+
+(defun sr-mirror-commit (mirror)
+  "Commit all modifications made to MIRROR in directory OVERLAY.
+Replaces the mirrored archive with a new one built with the
+current contents of the mirror. Keeps a backup of the original
+archive if the variable `sr-mirror-backup' is non-nil (the
+default)."
+  (condition-case err
+      (let ((repacked (sr-mirror-repack mirror))
+            (target (dired-get-filename)))
+        (if (and sr-mirror-keep-backups
+                 (not (sr-overlapping-paths-p sr-mirror-home target)))
+            (rename-file target (concat target ".bak") 1)
+          (delete-file target))
+        (copy-file repacked (dired-current-directory) t nil nil)
+        (delete-file repacked)
+        t)
+    (error (progn
+             (setq err (cadr err))
+             (if (not (yes-or-no-p (concat err ". OK to continue? ")))
+                 (error err))))))
+
+(defun sr-mirror-unmount (mirror overlay)
+  "Unmount and delete all directories used for mirroring a compressed archive.
+MIRROR is the union of the AVFS directory that holds the contents
+of the archive (read-only) with OVERLAY, which contains all the
+modifications made to the union in the current session."
+  (let* ((command (concat "cd ~; fusermount -u " sr-mirror-home mirror))
+         (err (shell-command-to-string command)))
+    (if (or (null err) (string= err ""))
+        (progn
+          (dired-delete-file (concat sr-mirror-home mirror) 'always)
+          (dired-delete-file (concat sr-mirror-home overlay) 'always)
+          (revert-buffer))
+      (error (concat "Sunrise: error unmounting mirror: " err)))))
+
+(defun sr-mirror-toggle ()
+  "Open new or destroy the current mirror area, depending on context."
+  (interactive)
+  (let ((open-ok) (close-ok) (err-msg))
+    (condition-case err1
+        (setq open-ok (sr-mirror-open))
+      (error (condition-case err2
+                 (progn
+                   (setq close-ok (sr-mirror-close))
+                   (setq err-msg (cadr err1)))
+               (error
+                  (setq err-msg (cadr err2))) )) )
+    (if (and (not open-ok) (not close-ok))
+        (error err-msg)
+      (sr-highlight))))
+
+(defun sr-mirror-repack (mirror)
+  "Try to repack the given MIRROR.
+On success, returns a string containing the full path to the newly
+packed archive, otherwise throws an error."
+  (message "Sunrise: repacking mirror, please wait...")
+  (let* ((target-home (concat sr-mirror-home ".repacked/"))
+         (archive (replace-regexp-in-string "#[a-z0-9#]*$" "" mirror))
+         (target (replace-regexp-in-string
+                  "/?$" ""
+                  (car (last (split-string archive "+")))))
+         (command (assoc-default archive sr-mirror-pack-commands-alist 'string-match)))
+
+    (if (null command)
+        (error (concat "Sunrise: sorry, don't know how to repack " mirror)))
+
+    (if (not (file-exists-p target-home))
+        (make-directory target-home))
+    (setq target (concat target-home target))
+    (setq command (replace-regexp-in-string "%f" target command))
+    (setq command (concat "cd " sr-mirror-home mirror "; " command))
+    (shell-command-to-string command)
+    target))
+
+(defun sr-mirror-mangle (path)
+  "Transform PATH into a string naming a new mirror area."
+  (let ((handler (assoc-default path sr-avfs-handlers-alist 'string-match)))
+    (if (eq ?/ (string-to-char path))
+        (setq path (substring path 1)))
+    (concat (replace-regexp-in-string
+             "/" "+"
+             (replace-regexp-in-string "\\+" "{+}" path)) handler)))
+
+(defun sr-mirror-demangle (path)
+  "Transform the given mirror area name into a regular filesystem path.
+Opposite of `sr-mirror-mangle'."
+  (concat "/"
+          (replace-regexp-in-string
+           "{\\+}" "+" (replace-regexp-in-string
+                        "\\+\\([^}]\\)" "/\\1" (replace-regexp-in-string
+                                                "#[a-z0-9#]*$" "" path)))))
+
+(defun sr-mirror-full-demangle (path)
+  "Demangle PATH recursively to obtain the current path of the original archive.
+This is necessary because reflecting an archive that is itself a
+reflection causes deadlocks in FUSE."
+  (let ((reflected path)
+        (home-len (length sr-mirror-home))
+        (handler (assoc-default path sr-avfs-handlers-alist 'string-match))
+        (prev-path))
+    (while (and (not (string= reflected prev-path))
+                (sr-overlapping-paths-p sr-mirror-home reflected))
+      (setq prev-path reflected)
+      (setq reflected (substring reflected home-len)
+            reflected (sr-mirror-demangle reflected)))
+    (setq reflected (concat sr-avfs-root reflected handler))
+    reflected))
+
+(defun sr-mirror-files (directory)
+  "Return list of pathnames constituting mirror modifications inside overlay DIRECTORY."
+  (if (not (file-directory-p directory))
+      (ignore)
+    (let ((files (directory-files directory)))
+      (mapc (lambda (x) (setq files (delete x files)))
+              '("." ".." "._funionfs_control~"))
+      files)))
+
+(defun sr-mirror-overlay-redir (dirname &optional force-root)
+  "Adjust DIRNAME for use with a mirror filesystem.
+Analyses the given directory path and rewrites it (if necessary)
+to play nicely with the mirror fs the given path belongs to. If
+the path is not inside any mirror fs, it is returned unmodified."
+  (if (null sr-avfs-root)
+      dirname
+    (let ((xpdir (expand-file-name dirname))
+          (mirror) (pos) (target))
+      (if (sr-overlapping-paths-p sr-mirror-home xpdir)
+          (progn
+            (setq mirror (substring xpdir (length sr-mirror-home)))
+            (setq pos (string-match "/\\|$" mirror))
+            (if pos
+                (progn
+                  (setq target (replace-regexp-in-string "^/" "" (substring mirror pos)))
+                  (setq mirror (substring mirror 0 pos))))
+            (if (and target
+                     (or (> (length target) 0) force-root)
+                     (not (eq ?. (string-to-char mirror))))
+                (concat sr-mirror-home "." mirror "/" target)
+              dirname))
+        dirname))))
+
+(defun sr-mirror-surface (dir)
+  "Return the topmost parent of DIR under `sr-mirror-home', if any."
+  (if (and sr-mirror-home
+           (sr-overlapping-paths-p sr-mirror-home dir)
+           (not (sr-equal-dirs sr-mirror-home dir)))
+      (let ((local-dir (dired-make-relative dir sr-mirror-home)))
+        (string-match "^\\([^/]*\\)" local-dir)
+        (match-string 1 local-dir))))
+
+(defun sr-mirror-overlapping-p (mirror1 mirror2)
+  "Return non-nil if the surface of MIRROR2 maps an archive nested
+inside the archive mapped by the surface of MIRROR1."
+  (let ((surface1 (sr-mirror-surface mirror1))
+        (surface2 (sr-mirror-surface mirror2))
+        top)
+    (when (and surface1 surface2)
+      (setq top (sr-mirror-demangle surface1))
+      (sr-overlapping-paths-p top (sr-mirror-demangle surface2)))))
+
+(defun sr-mirror-goto-dir (target)
+  "Enhance `sr-goto-dir' with transparent navigation inside mirror areas.
+All calls to `sr-goto-dir' are diverted to this function."
+  (let* ((here (expand-file-name default-directory))
+         (target (expand-file-name (or target ".")))
+         (surface-here (sr-mirror-surface here))
+         (sr-mirror-divert-goto-dir nil)
+         surface-target)
+    (cond
+     ((null surface-here) (sr-goto-dir target))
+     ((sr-overlapping-paths-p sr-avfs-root target) (sr-mirror-open))
+     (t
+      (progn
+        (if (sr-equal-dirs target sr-mirror-home)
+            (setq target (expand-file-name
+                          (concat (sr-mirror-demangle surface-here) "/.."))
+                  surface-target (sr-mirror-surface (sr-mirror-mangle target)))
+          (setq surface-target (sr-mirror-surface target)))
+        (unless (equal surface-here surface-target)
+          (if (and surface-target
+                   (sr-overlapping-paths-p sr-mirror-home target)
+                   (sr-mirror-overlapping-p surface-target surface-here))
+              (sr-mirror-close t t)
+            (sr-mirror-close nil nil t)))
+        (unless (or (not (file-directory-p target))
+                    (sr-equal-dirs target (dired-current-directory)))
+          (sr-goto-dir target)))))
+    (sr-highlight)))
+
+(defun sr-mirror-on-kill-buffer ()
+  "Handle navigation out of a mirror area other than through `sr-goto-dir'.
+This includes e.g. bookmark jumps and pane synchronizations."
+  (when (and sr-mirror-home (eq major-mode 'sr-mode)
+           (null (sr-mirror-surface sr-this-directory))
+           (sr-mirror-surface (dired-current-directory)))
+      (sr-mirror-goto-dir sr-this-directory)
+      (sr-unhighlight 'sr-mirror-path-face)))
+
+(defadvice sr-goto-dir
+  (around sr-mirror-advice-sr-goto-dir (dir))
+  "Divert all `sr-goto-dir' calls to `sr-mirror-goto-dir'."
+  (if sr-mirror-divert-goto-dir
+      (sr-mirror-goto-dir dir)
+    ad-do-it))
+
+(defadvice sr-clone-files
+  (around sr-mirror-advice-sr-clone-files
+          (file-path-list target-dir clone-op progress &optional do-overwrite))
+"Redirect all `sr-copy' operations to the right path under the
+overlay directory."
+  (if (null sr-mirror-home)
+      ad-do-it
+    (let ((orig target-dir))
+      (setq target-dir (sr-mirror-overlay-redir target-dir t))
+      (if (> (length target-dir) (length orig))
+          (make-directory target-dir))
+      ad-do-it)))
+(ad-activate 'sr-clone-files)
+
+(defadvice make-directory
+  (around sr-mirror-advice-make-directory (dirname &optional parents))
+  "Redirect directory creation operations to the right path under
+the overlay directory."
+  (setq dirname (sr-mirror-overlay-redir dirname))
+  (setq parents t)
+  ad-do-it)
+
+(defadvice save-buffer
+  (around sr-mirror-advice-save-buffer (&optional args))
+  "Create all the subdirectories (and set their permissions)
+needed for enabling the redirection of buffer saving operations
+to the right path under the overlay directory."
+  (let* ((orig (buffer-file-name))
+         (target (sr-mirror-overlay-redir orig)))
+    (if (> (length target) (length orig))
+        (let ((default-directory "~/")
+              (target-dir (file-name-directory target)))
+          (make-directory target-dir)
+          (shell-command-to-string (concat dired-chmod-program " a+x " target-dir))
+          (write-file target nil))
+      ad-do-it)))
+
+(defun sr-mirror-toggle-read-only ()
+  "Toggle the read-only flag in all buffers opened inside a mirror area,
+so they are always writeable by default."
+  (if sr-mirror-home
+      (let* ((orig (buffer-file-name))
+             (target (sr-mirror-overlay-redir orig)))
+        (if (> (length target) (length orig))
+            (setq buffer-read-only nil)))))
+(add-hook 'find-file-hook 'sr-mirror-toggle-read-only)
+
+(defun sunrise-x-mirror-unload-function ()
+  (sr-ad-disable "^sr-mirror-"))
+
+(provide 'sunrise-x-mirror)
+
+;;;###autoload (eval-after-load 'sunrise-commander '(sr-extend-with 'sunrise-x-mirror))
+
+;;; sunrise-x-mirror.el ends here
diff --git a/elisp/sunrise/sunrise-x-modeline.el b/elisp/sunrise/sunrise-x-modeline.el
new file mode 100644 (file)
index 0000000..569c0b5
--- /dev/null
@@ -0,0 +1,329 @@
+;;; sunrise-x-modeline.el --- navigable mode line for the Sunrise Commander File Manager -*- lexical-binding: t -*-
+
+;; Copyright (C) 2009-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 10 Oct 2009
+;; Version: 2
+;; RCS Version: $Rev: 423 $
+;; Keywords: sunrise commander, modeline, path mode line
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-modeline.el
+;; Compatibility: GNU Emacs 22+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This extension modifies the format of the mode lines under the Sunrise
+;; Commander panes so they display only the paths to the current directories (or
+;; the tail if the whole path is too long) and a row of three small icons. These
+;; icons are by default plain ASCII characters, but nicer semigraphical versions
+;; (in Unicode) can also be used by customizing the variable
+;; `sr-modeline-use-utf8-marks'.
+;;
+;; Here is the complete list of indicator icons (in ASCII and Unicode) and their
+;; respective meanings:
+;;                      (ascii) (unicode)
+;; 1. Pane modes:          *        ☼     Normal mode.
+;;                         !        ⚡     Editable Pane mode.
+;;                         @        ☯     Virtual Directory mode.
+;;                         T        ⚘     Tree View mode (with tree extension).
+;;
+;; 2. Navigation modes:    &        ⚓     Synchronized Navigation.
+;;                         $        ♻     Sticky Search.
+;;
+;; 3. Transient states:    #        ♥     Contents snapshot available.
+;;
+;; (if you can't see the icons on the right, don't use utf8 marks)
+
+;; The regular mode line format remains available: press C-c m to toggle between
+;; one format and the other.
+
+;; The extension is provided as a minor mode, so you can enable / disable it
+;; totally by issuing the command `sr-modeline'.
+
+;; It was written on GNU Emacs 24 on Linux, and tested on GNU Emacs 22 and 23
+;; for Linux and on EmacsW32 (version 22) for Windows.
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'.
+
+;; 2) Add a (require 'sunrise‐x‐modeline) expression to your .emacs file
+;; somewhere after the (require 'sunrise‐commander) one.
+
+;; 3) Evaluate the new expression, or reload your .emacs file, or restart Emacs.
+
+;; 4) Enjoy ;-)
+
+;;; Code:
+
+(require 'sunrise-commander)
+(require 'desktop)
+(require 'easymenu)
+(eval-when-compile (require 'cl))
+
+(defcustom sr-modeline-use-utf8-marks nil
+  "Set to t to use fancy marks (using UTF-8 glyphs) in the mode line."
+  :group 'sunrise
+  :type 'boolean)
+
+;; slot 0 -- pane modes:
+(defconst sr-modeline-norm-mark '("*" . "☼"))
+(defconst sr-modeline-edit-mark '("!" . "⚡"))
+(defconst sr-modeline-virt-mark '("@" . "☯"))
+(defconst sr-modeline-tree-mark '("T" . "⚘"))
+
+;; slot 1 -- navigation modes:
+(defconst sr-modeline-sync-mark '("&" . "⚓"))
+(defconst sr-modeline-srch-mark '("$" . "♻"))
+
+;; slot 2 -- transient states:
+(defconst sr-modeline-bkup-mark '("#" . "♥"))
+
+(defface sr-modeline-separator-face
+  '((t (:height 0.3)))
+  "Face of the string used to separate the state indicators from one another."
+  :group 'sunrise)
+
+(defconst sr-modeline-sep #(" " 0 1 (face sr-modeline-separator-face))
+  "Sunrise Modeline separator character.")
+
+;;; ============================================================================
+;;; Core functions:
+
+(defvar sr-modeline-mark-map (make-sparse-keymap))
+(define-key sr-modeline-mark-map [mode-line mouse-1] 'sr-modeline-popup-menu)
+(define-key sr-modeline-mark-map [mode-line mouse-2] 'sr-modeline-popup-menu)
+
+(defvar sr-modeline-path-map (make-sparse-keymap))
+(define-key sr-modeline-path-map [mode-line mouse-1] 'sr-modeline-navigate-path)
+(define-key sr-modeline-path-map [mode-line mouse-2] 'sr-modeline-navigate-path)
+
+(defun sr-modeline-select-mark (mark &optional slot)
+  "Select the right character for the given MARK in SLOT.
+Depends on whether UTF-8 has been enabled in the mode line via
+the variable `sr-modeline-use-utf8-marks'."
+  (let ((select (if sr-modeline-use-utf8-marks #'cdr #'car))
+        (slot (or slot 0)))
+    (case slot
+      (0 (funcall select (case mark
+                           (edit sr-modeline-edit-mark)
+                           (virt sr-modeline-virt-mark)
+                           (tree sr-modeline-tree-mark)
+                           (t sr-modeline-norm-mark))))
+      (1 (cond ((or (memq 'sr-sticky-post-isearch isearch-mode-end-hook)
+                    (memq 'sr-tree-post-isearch isearch-mode-end-hook))
+                (funcall select sr-modeline-srch-mark))
+               (sr-synchronized
+                (funcall select sr-modeline-sync-mark))
+               (t " ")))
+      (t (if (buffer-live-p sr-backup-buffer)
+             (funcall select sr-modeline-bkup-mark)
+           " ")))))
+
+(defun sr-modeline-select-mode (mode)
+  "Assemble the indicators section on the left of the modeline."
+  (concat sr-modeline-sep (sr-modeline-select-mark mode 0)
+          sr-modeline-sep (sr-modeline-select-mark mode 1)
+          sr-modeline-sep (sr-modeline-select-mark mode 2)
+          sr-modeline-sep))
+
+(defun sr-modeline-setup ()
+  "Determine the mode indicator (character) to display in the mode line.
+On success, sets the mode line format by calling
+`sr-modeline-set'."
+  (let ((mode nil))
+    (case major-mode
+      (sr-mode
+       (setq mode (sr-modeline-select-mode (if buffer-read-only 'norm 'edit))))
+      (sr-tree-mode
+       (setq mode (sr-modeline-select-mode 'tree)))
+      (sr-virtual-mode
+       (setq mode (sr-modeline-select-mode 'virt))))
+    (if mode (sr-modeline-set mode))))
+
+(defun sr-modeline-set (mark)
+  "Adjust the current mode line format.
+Uses the given mode indicator and the path to the current
+directory of the pane. Truncates the path if it is longer than
+the available width of the pane."
+  (let ((path (expand-file-name default-directory))
+        (path-length (length default-directory))
+        (max-length (- (window-width) 12)))
+    (if (< max-length path-length)
+        (setq path (concat "..." (substring path (- path-length max-length)))))
+    (eval
+     `(setq mode-line-format
+            '("%[" ,(sr-modeline-mark mark) "%] " ,(sr-modeline-path path))))))
+
+(defun sr-modeline-mark (marks-string)
+  "Propertize MARKS-STRING for use in displaying the mode line indicators."
+  (let ((mode-name "") (marks (split-string marks-string "|")))
+    (setq mode-name
+          (concat
+           (cond ((member (sr-modeline-select-mark 'edit) marks)
+                  "Editable Pane Mode")
+                 ((member (sr-modeline-select-mark 'virt) marks)
+                  "Virtual Directory Mode")
+                 ((member (sr-modeline-select-mark 'tree) marks)
+                  "Tree View Mode")
+                 (t "Normal Mode"))
+           (if sr-synchronized " | Synchronized Navigation" "")
+           (if (or (memq 'sr-sticky-post-isearch isearch-mode-end-hook)
+                  (memq 'sr-tree-post-isearch isearch-mode-end-hook))
+              " | Sticky Search"
+            "")
+           (if (buffer-live-p sr-backup-buffer) " | Snapshot Available" "")))
+    (propertize marks-string
+                'font 'bold
+                'mouse-face 'mode-line-highlight
+                'help-echo (format "Sunrise Commander: %s" mode-name)
+                'local-map sr-modeline-mark-map)))
+
+(defun sr-modeline-path (path)
+  "Propertize the string PATH for use in the mode line format.
+PATH is the current directory in the file system."
+  (propertize path
+              'local-map sr-modeline-path-map
+              'mouse-face 'mode-line-highlight
+              'help-echo "Click to navigate directory path"
+              'sr-selected-window sr-selected-window))
+
+(defun sr-modeline-navigate-path ()
+  "Handle click events occuring on the mode line directory path.
+Analyzes all click events detected on the directory path and
+modifies the current directory of the corresponding panel
+accordingly."
+  (interactive)
+  (let* ((event (caddr (cddadr last-input-event)))
+         (path (car event)) (pos (cdr event)) (slash) (levels))
+    (or (eq sr-selected-window (get-text-property 0 'sr-selected-window path))
+        (sr-change-window))
+    (setq slash (string-match "/" path pos)
+          levels (- (length (split-string (substring path slash) "/")) 2))
+    (if (< 0 levels)
+        (sr-dired-prev-subdir levels)
+      (sr-beginning-of-buffer))))
+
+;;; ============================================================================
+;;; Private interface:
+
+(defvar sr-modeline)
+
+(defun sr-modeline-refresh ()
+  (setq sr-modeline t)
+  (sr-modeline-setup))
+
+(defun sr-modeline-engage ()
+  "Activate and enforce the navigation mode line format."
+  (add-hook 'sr-refresh-hook 'sr-modeline-refresh)
+  (sr-modeline-setup)
+  (sr-in-other (sr-modeline-setup)))
+
+(defun sr-modeline-disengage ()
+  "De-activate the navigation mode line format, restoring the default one."
+  (remove-hook 'sr-refresh-hook 'sr-modeline-refresh)
+  (setq mode-line-format (default-value 'mode-line-format))
+  (sr-in-other (setq mode-line-format (default-value 'mode-line-format))))
+
+(defun sr-modeline-toggle (&optional force)
+  ;; FIXME explain the argument
+  "Toggle display of the navigation mode line format."
+  (interactive)
+  (cond ((and force (< 0 force)) (sr-modeline-engage))
+        ((and force (> 0 force)) (sr-modeline-disengage))
+        (t
+         (if (eq mode-line-format (default-value 'mode-line-format))
+             (sr-modeline-engage)
+           (sr-modeline-disengage)))))
+
+;;; ============================================================================
+;;; User interface:
+
+(defvar sr-modeline-map (make-sparse-keymap))
+(define-key sr-modeline-map "\C-cm" 'sr-modeline-toggle)
+
+(define-minor-mode sr-modeline
+  "Provide navigable mode line for the Sunrise Commander.
+This is a minor mode that provides a single keybinding:
+
+  C-c m ................ Toggle between navigation and default mode line formats
+
+  To totally disable this extension do: M-x sr-modeline <RET>"
+
+  nil (sr-modeline-select-mode 'norm) sr-modeline-map
+  (unless (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+    (setq sr-modeline nil)
+    (error "Sorry, this mode can be used only within the Sunrise Commander"))
+  (sr-modeline-toggle 1))
+
+(defvar sr-modeline-menu
+  (easy-menu-create-menu
+   "Mode Line"
+   '(["Toggle navigation mode line" sr-modeline-toggle t]
+     ["Navigation mode line help" (lambda ()
+                                    (interactive)
+                                    (describe-function 'sr-modeline))] )))
+(defun sr-modeline-popup-menu ()
+  (interactive)
+  (popup-menu sr-modeline-menu))
+
+;;; ============================================================================
+;;; Bootstrap:
+
+(defun sr-modeline-menu-init ()
+  "Initialize the Sunrise Mode Line extension menu."
+  (unless (lookup-key sr-mode-map [menu-bar Sunrise])
+    (define-key sr-mode-map [menu-bar Sunrise]
+      (cons "Sunrise" (make-sparse-keymap))))
+  (let ((menu-map (make-sparse-keymap "Mode Line")))
+    (define-key sr-mode-map [menu-bar Sunrise mode-line]
+      (cons "Mode Line" menu-map))
+    (define-key menu-map [help] '("Help" . (lambda ()
+                                             (interactive)
+                                             (describe-function 'sr-modeline))))
+    (define-key menu-map [disable] '("Toggle" . sr-modeline-toggle))))
+
+(defun sr-modeline-start-once ()
+  "Bootstrap the navigation mode line on the first execution of
+the Sunrise Commander, after module installation."
+  (sr-modeline t)
+  (sr-modeline-menu-init)
+  (remove-hook 'sr-start-hook 'sr-modeline-start-once)
+  (unintern 'sr-modeline-menu-init obarray)
+  (unintern 'sr-modeline-start-once obarray))
+(add-hook 'sr-start-hook 'sr-modeline-start-once)
+
+;;; ============================================================================
+;;; Desktop support:
+
+(add-to-list 'desktop-minor-mode-table '(sr-modeline nil))
+
+(defun sr-modeline-desktop-restore-function (&rest _)
+  "Call this instead of `sr-modeline' when restoring a desktop."
+  (sr-modeline-refresh))
+
+(add-to-list 'desktop-minor-mode-handlers
+             '(sr-modeline . sr-modeline-desktop-restore-function))
+
+(provide 'sunrise-x-modeline)
+
+;;;###autoload (eval-after-load 'sunrise-commander '(sr-extend-with 'sunrise-x-modeline))
+
+;;; sunrise-x-modeline.el ends here
diff --git a/elisp/sunrise/sunrise-x-old-checkpoints.el b/elisp/sunrise/sunrise-x-old-checkpoints.el
new file mode 100644 (file)
index 0000000..34debc3
--- /dev/null
@@ -0,0 +1,90 @@
+;;; sunrise-x-old-checkpoints.el --- backward compatibility checkpoint functions for the Sunrise Commander File Manager
+
+;; Copyright (C) 2009-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 28 Dec 2009
+;; Version: 1
+;; RCS Version: $Rev: 427 $
+;; Keywords: sunrise commander, old checkpoints
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-old-checkpoints.el
+;; Compatibility: GNU Emacs 22
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Beginning with version 4 of the Sunrise Commander, checkpoints were redefined
+;; to be a special form of bookmarks. Unfortunately, creating bookmarks with
+;; custom handlers isn't supported in the version of bookmarks.el distributed
+;; with Emacs 22, so if you use Sunrise checkpoints and you don't want to update
+;; your bookmarks.el, just add this extension to your .emacs.el to get back the
+;; original functionality.
+
+;; This extension was written on GNU Emacs 23 on Linux, and tested on GNU Emacs
+;; 22 and 23 for Linux and on EmacsW32 (version 22) for Windows.
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'. (Optionally) compile
+;; it.
+
+;; 2) Enjoy ;-) -- Sunrise should pick the correct extension automatically. On
+;; Emacs 23 it will look for sunrise-x-checkpoints, while on Emacs 22 it'll try
+;; to load sunrise-x-old-checkpoints. Only if you *really* want to use the old
+;; extensions with a more recent version of bookmarks.el than the one bundled
+;; with Emacs 22 you may add a new (require 'sunrise-x-old-checkpoints) to your
+;; .emacs file somewhere after (require 'sunrise-commander).
+
+;;; Code:
+
+(eval-when-compile (require 'sunrise-commander))
+
+(defvar sr-checkpoint-registry '(("~" "~/" "~/"))
+  "Registry of currently defined checkpoints.")
+
+(defun sr-checkpoint-save (&optional name)
+  "Save the current Sunrise pane directories under NAME for later restoring."
+  (interactive "sCheckpoint name to save? ")
+  (let ((my-cell (assoc-string name sr-checkpoint-registry)))
+    (sr-save-directories)
+    (if (null my-cell)
+        (setq sr-checkpoint-registry
+              (cons (cons name (list sr-left-directory sr-right-directory))
+                    sr-checkpoint-registry))
+      (setcdr my-cell (list sr-left-directory sr-right-directory)))
+  (message "%s" (concat "Checkpoint \"" name "\" saved"))))
+
+(defun sr-checkpoint-restore (&optional name)
+  "Restore a checkpoint previously saved under NAME."
+  (interactive "sCheckpoint name to restore? " )
+  (let* ((cp-list (assoc-string name sr-checkpoint-registry))
+         (dirs-list (cdr cp-list)))
+    (unless cp-list
+      (error (concat "No such checkpoint: " name)))
+    (if (eq sr-selected-window 'right)
+        (setq dirs-list (reverse dirs-list)))
+    (mapc (lambda (x) (sr-goto-dir x) (sr-change-window)) dirs-list)))
+
+(defun sr-checkpoint-handler (&optional arg)
+  "Dummy function for compatilibity with the new checkpoints interface."
+  (ignore))
+
+(provide 'sunrise-x-old-checkpoints)
+
+;;; sunrise-x-old-checkpoints.el ends here
diff --git a/elisp/sunrise/sunrise-x-popviewer.el b/elisp/sunrise/sunrise-x-popviewer.el
new file mode 100644 (file)
index 0000000..9839f05
--- /dev/null
@@ -0,0 +1,230 @@
+;;; sunrise-x-popviewer.el --- floating viewer window for the Sunrise Commander -*- lexical-binding: t -*-
+
+;; Copyright (C) 2008-2012 José Alfredo Romero L.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 20 Aug 2008
+;; Version: 3
+;; RCS Version: $Rev: 444 $
+;; Keywords: sunrise commander, windows, accessibility, viewer
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-popviewer.el
+;; Compatibility: GNU Emacs 22+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This extension advises several Sunrise Commander functions in order to make
+;; the viewer window "float", i.e. instead of having a dedicated window sitting
+;; under the panes all the time, a new frame is displayed whenever the user
+;; requests to view a file (by pressing "o" or "v") or to open a command line in
+;; the current directory.
+
+;; WARNING: This code and the Buttons extension (sunrise-x-buttons) do NOT mix
+;; together, if you're using the Buttons extension remove it first from your
+;; .emacs file.
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'.
+
+;; 2) If you are currently using the Buttons extension (sunrise-x-buttons),
+;; remove it first from your .emacs file.
+
+;; 2) Add a (require 'sunrise-x-popviewer) expression to your .emacs file
+;; somewhere after the (require 'sunrise-commander) one.
+
+;; 3) Evaluate the new expression, or reload your .emacs file, or restart Emacs.
+
+;; 4) Use `sr-popviewer-mode' to toggle the functionality.
+
+;; 5) The next time you invoke the Sunrise Commander, only two panes will be
+;; displayed. If you press o (or v) on a file inside any of them, it will be
+;; displayed in a new frame. If you press C-c t to open a terminal in the
+;; current directory, it'll be opened in a new frame too.
+
+;; 6) Enjoy ;-)
+
+;;; Code:
+
+(require 'sunrise-commander)
+(eval-when-compile (require 'cl))
+
+(defcustom sr-popviewer-enabled nil
+  "Whether the popviewer extension should be active at startup."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-popviewer-style 'dedicated-frames
+  "Determines the way frames are used for quick viewing files:
+
+* Single Frame: reuse the same frame whenever possible.
+* Single Dedicated Frame: reuse and close frame when its last buffer is killed.
+* Multiple Frames: use a new frame for every new file (or terminal) displayed.
+* Dedicated Frames: use a new frame and close it whenever its buffer is killed."
+  :group 'sunrise
+  :type '(choice
+          (const single-frame)
+          (const single-dedicated-frame)
+          (const multiple-frames)
+          (const dedicated-frames)))
+
+(defcustom sr-popviewer-select-viewer-action nil
+  "Alternative function for selecting a viewer window"
+  :group 'sunrise
+  :type 'function)
+
+(defvar sr-popviewer-frame-name "Sunrise Viewer"
+  "Name of the frame being currently used as the viewer.")
+
+(defun sr-popviewer-setup-windows ()
+  "`sr-setup-windows' replacement for `sr-popviewer-mode'."
+  (interactive)
+  (bury-buffer)
+  (delete-other-windows)
+
+  (case sr-window-split-style
+    (horizontal (split-window-horizontally))
+    (vertical   (split-window-vertically))
+    (top        (ignore))
+    (t (error "Sunrise: don't know how to split this window: %s" sr-window-split-style)))
+
+  (sr-setup-visible-panes)
+  (sr-select-window sr-selected-window)
+  (sr-restore-panes-width)
+  (setq other-window-scroll-buffer nil)
+  (run-hooks 'sr-start-hook))
+
+(defadvice sr-setup-windows
+  (around sr-popviewer-advice-setup-windows)
+  "Set up the Sunrise window configuration (two windows in `sr-mode')."
+  (sr-popviewer-setup-windows))
+
+(defun sr-popviewer-get-frame ()
+  "Return the frame being currently used as the viewer, if any."
+  (cdr (assoc sr-popviewer-frame-name (make-frame-names-alist))))
+
+(defun sr-popviewer-pop-frame ()
+  "Bring forward the viewer frame, create a new one if necessary."
+  (let* ((vframe (sr-popviewer-get-frame)) (target-frame))
+    (when vframe
+      (select-frame vframe)
+      (if (memq sr-popviewer-style '(single-frame single-dedicated-frame))
+          (setq target-frame vframe)
+        (set-frame-name (buffer-name))))
+    (unless target-frame
+      (setq other-window-scroll-buffer nil)
+      (setq target-frame (make-frame `((name . ,sr-popviewer-frame-name)))))
+    (select-frame target-frame)
+    (raise-frame)))
+
+(defun sr-popviewer-dedicate-frame ()
+  "Take care of dedicating the current window as to its frame, if necessary."
+  (let ((vframe (sr-popviewer-get-frame)))
+    (when vframe
+      (select-frame vframe)
+      (set-window-dedicated-p
+       (frame-first-window vframe)
+       (memq sr-popviewer-style '(single-dedicated-frame dedicated-frames))))
+    (add-hook
+     'kill-buffer-hook (lambda () (sr-select-window sr-selected-window)) t t)))
+
+(defun sr-popviewer-quick-view (&optional arg)
+  "Quickly view the currently selected item.
+On regular files, it opens the file in a separate frame, on
+directories visits the selected directory in the passive pane,
+and on symlinks follows the file the link points to in the
+passive pane."
+  (interactive "P")
+  (setq
+   other-window-scroll-buffer
+   (let ((other-window-scroll-buffer
+          (if (memq sr-popviewer-style '(single-frame single-dedicated-frame))
+              other-window-scroll-buffer
+            nil)))
+     (sr-quick-view arg)
+     (sr-popviewer-dedicate-frame)
+     other-window-scroll-buffer)))
+(defadvice sr-term
+  (around sr-popviewer-advice-term (&optional cd newterm program))
+  "Make terminal windows dedicated when using multiple viewers."
+  (let ((sr-popviewer-style (if (or newterm program)
+                                sr-popviewer-style
+                              'single-frame)))
+    ad-do-it)
+  (sr-popviewer-dedicate-frame))
+
+(defun sr-popviewer-select-viewer-window ()
+  "Popviewer replacement for `sr-select-viewer-window'."
+  (interactive)
+  (cond (sr-popviewer-select-viewer-action
+          (funcall sr-popviewer-select-viewer-action))
+        ((null window-system) (other-window 1))
+        (t (sr-popviewer-pop-frame))))
+
+(defadvice sr-select-viewer-window
+  (around sr-popviewer-advice-select-viewer-window)
+  "Try to select a window that is not a SC pane in a separate frame."
+  (sr-popviewer-select-viewer-window))
+
+(defadvice sunrise-cd
+  (around sr-popviewer-advice-sunrise-cd (&optional norestore))
+  "Redefine `sunrise-cd' not to disable Sunrise in PopViewer mode."
+  (if sr-running
+      (sr-popviewer-setup-windows)
+    ad-do-it))
+
+(define-minor-mode sr-popviewer-mode "Use an alternative viewer window."
+  :global t
+  :group 'sunrise
+  :lighter ""
+  (let ((hookfun (if sr-popviewer-mode 'remove-hook 'add-hook))
+        (adfun (if sr-popviewer-mode 'sr-ad-enable 'sr-ad-disable))
+        (viewerfun (if sr-popviewer-mode
+                        'sr-popviewer-select-viewer-window
+                      'sr-select-viewer-window))
+        (quickviewfun (if sr-popviewer-mode
+                          'sr-popviewer-quick-view
+                        'sr-quick-view))
+        (panelockfun (if sr-popviewer-mode
+                         'sr-popviewer-setup-windows
+                       'sr-lock-panes)))
+    (funcall hookfun 'window-size-change-functions 'sr-lock-window)
+    (define-key sr-mode-map "o" quickviewfun)
+    (define-key sr-mode-map "v" quickviewfun)
+    (define-key sr-mode-map "\C-c\t" viewerfun)
+    (define-key sr-mode-map [(control tab)] viewerfun)
+    (define-key sr-mode-map "\\" panelockfun)
+    (funcall adfun "^sr-popviewer-")
+    (if sr-running (sr-setup-windows))))
+
+(defun sunrise-x-popviewer-unload-function ()
+  (sr-popviewer-mode -1)
+  (sr-ad-disable "^sr-popviewer-"))
+
+(sr-popviewer-mode (if sr-popviewer-enabled 1 -1))
+(provide 'sunrise-x-popviewer)
+
+;;;###autoload (eval-after-load 'sunrise-commander '(sr-extend-with 'sunrise-x-popviewer))
+
+;;; sunrise-x-popviewer.el ends here
diff --git a/elisp/sunrise/sunrise-x-tabs.el b/elisp/sunrise/sunrise-x-tabs.el
new file mode 100644 (file)
index 0000000..f761bc6
--- /dev/null
@@ -0,0 +1,659 @@
+;;; sunrise-x-tabs.el --- tabs for the Sunrise Commander File Manager -*- lexical-binding: t -*-
+
+;; Copyright (C) 2009-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 24 Oct 2009
+;; Version: 1
+;; RCS Version: $Rev: 445 $
+;; Keywords: sunrise commander, tabs
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-tabs.el
+;; Compatibility: GNU Emacs 22+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This extension brings tab‐based navigation to the Sunrise Commander. It adds
+;; to the list of optional mechanisms already available in Sunrise for moving
+;; around the file system (like regular bookmarks, checkpoints, history rings,
+;; materialized virtual buffers, navigable paths and file‐following) another way
+;; to maintain a list of selected locations one wants to return later on, or to
+;; compose "breadcrumb trails" for complex repetitive operations.
+
+;; The main difference between tabs and other mechanisms is that once a buffer
+;; has been assigned to a tab, it will not be killed automatically by Sunrise,
+;; so it's possible to keep it around as long as necessary with all its marks
+;; and state untouched. Tabs can be persisted across sessions using the DeskTop
+;; feature.
+
+;; Creating, using and destroying tabs are fast and easy operations, either with
+;; mouse or keyboard:
+
+;; * Press C-j (or select Sunrise > Tabs > Add Tab in the menu) to create a new
+;; tab or to rename an already existing tab.
+
+;; * Press C-k (or right-click the tab) to kill an existing tab. Combine with M-
+;; (M-C-k) to kill the tab on the passive pane. Prefix with a digit to kill tabs
+;; by relative order (e.g. 2 C-k kills the second tab in the current pane, while
+;; 4 M-C-k kills the fourth tab in the passive pane).
+
+;; * Press C‐n and C‐p to move from tab to tab ("Next", "Previous"), or simply
+;; left‐click on the tab to focus its assigned buffer. These two keybindings can
+;; be prefixed with an integer to move faster.
+
+;; * The last four bindings can be combined with Meta (i.e. M‐C‐j, M‐C‐k, M‐C‐n
+;; and M‐C‐p) to perform the equivalent operation on the passive pane or (when
+;; in synchronized navigation mode) on both panes simultaneously.
+
+;; * Press * C-k to kill in one go all the tabs in the current pane. Similarly,
+;; press * M-C-k to wipe all the tabs off the passive pane or (when synchronized
+;; mode is active) on both panes simultaneously.
+
+;; * Killing the current buffer with C‐x k automatically switches to the one
+;; assigned to the first available tab (if any).
+
+;; The extension is provided as a minor mode, so you can enable / disable it
+;; totally by using the command `sr-tabs-mode'.
+
+;; It does *not* pretend to be a generic solution for tabs in Emacs. If you need
+;; one, have a look at TabBar mode (http://www.emacswiki.org/emacs/TabBarMode)
+;; by David Ponce. I wrote this just because it turned out to be easier to write
+;; this than to customize tabbar to behave exactly like I wanted inside the
+;; Sunrise panes. It's meant to be simple and to work nicely with Sunrise with
+;; just a few tabs (up to 10‐15 per pane, maybe).
+
+;; It was written on GNU Emacs 23 on Linux, and tested on GNU Emacs 22 and 23
+;; for Linux and on EmacsW32 (version 23) for Windows.
+
+;;; Installation and Usage:
+
+;; 1) Put this file somewhere in your Emacs `load-path'.
+
+;; 2) Add a (require 'sunrise‐x‐tabs) expression to your .emacs file somewhere
+;; after the (require 'sunrise‐commander) one.
+
+;; 3) Evaluate the new expression, or reload your .emacs file, or restart Emacs.
+
+;; 4) Enjoy ;-)
+
+;;; Code:
+
+(require 'sunrise-commander)
+(eval-when-compile (require 'cl)
+                   (require 'desktop))
+
+(defcustom sr-tabs-follow-panes t
+  "Whether tabs should be swapped too when transposing the Sunrise panes."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-tabs-max-tabsize 10
+  "Maximum width of a Sunrise Commander tab."
+  :group 'sunrise
+  :type 'integer)
+
+(defface sr-tabs-active-face
+  '((((type tty) (class color) (min-colors 88))
+     :background "white")
+    (((type tty) (class color) (min-colors 8))
+     :background "green" :foreground "yellow" :bold t)
+    (((type tty) (class mono)) :inverse-video t)
+    (t
+     :inherit variable-pitch :bold t :background "white" :height 0.9))
+  "Face of the currently selected tab in any of the Sunrise panes."
+  :group 'sunrise)
+
+(defface sr-tabs-inactive-face
+  '((((type tty) (class color) (min-colors 88))
+     :background "color-84" :foreground "white")
+    (((type tty) (class color) (min-colors 8))
+     :background "white" :foreground "cyan")
+    (t
+     :inherit variable-pitch :background "gray95" :height 0.9))
+  "Face of all non-selected tabs in both Sunrise panes."
+  :group 'sunrise)
+
+(defface sr-tabs-separator-face
+  '((t (:height 0.3)))
+  "Face of the string used to separate the Sunrise tabs from one another."
+  :group 'sunrise)
+
+(defconst sr-tabs-sep #(" " 0 1 (face sr-tabs-separator-face))
+  "Sunrise Tabs separator character.")
+
+(defconst sr-tabs-ligature #(" ║" 0 1 (face sr-tabs-separator-face))
+  "Sunrise Tabs line separator string.")
+
+(defconst sr-tabs-max-cache-length 30
+  "Max number of tab labels cached for reuse.")
+
+(defvar sr-tabs '((left) (right)))
+(defvar sr-tabs-labels-cache '((left) (right)))
+(defvar sr-tabs-line-cache '((left) (right)))
+(defvar sr-tabs-mode nil)
+(defvar sr-tabs-on nil)
+
+;;; ============================================================================
+;;; Core functions:
+
+(defun sr-tabs-add ()
+  "Assign the current buffer to exactly one tab in the active pane.
+If a tab for the current buffer already exists, invoke `sr-tabs-rename'."
+  (interactive)
+  (let ((tab-name (buffer-name))
+        (tab-set (assq sr-selected-window sr-tabs)))
+      (if (member tab-name (cdr tab-set))
+          (call-interactively 'sr-tabs-rename)
+        (setcdr tab-set (cons tab-name (cdr tab-set)))))
+  (sr-tabs-refresh))
+
+(defun sr-tabs-remove (&optional tab-buffer side)
+  "Remove the tab to which TAB-BUFFER is assigned in the active pane.
+If TAB-BUFFER is nil, removes the tab to which the current buffer
+is assigned, if any."
+  (interactive "P")
+  (let* ((side (or side sr-selected-window))
+         (tab-name (if (integerp tab-buffer)
+                       (nth tab-buffer (assoc side sr-tabs))
+                     (buffer-name tab-buffer)))
+         (tab-buffer (and tab-name (get-buffer tab-name)))
+         (tab-set (assq side sr-tabs)))
+    (setcdr tab-set (delete tab-name (cdr tab-set)))
+    (unless (or (null tab-buffer)
+                (eq tab-buffer (current-buffer))
+                (eq tab-buffer (sr-other 'buffer)))
+      (kill-buffer (get-buffer tab-name))))
+  (sr-tabs-refresh))
+
+(defun sr-tabs-clean ()
+  "Remove all tabs from the current pane."
+  (interactive)
+  (while (nth 1 (assoc sr-selected-window sr-tabs))
+    (sr-tabs-remove 1)))
+
+(defun sr-tabs-kill (&optional name side)
+  "Remove the tab named NAME from the active pane and kill its buffer.
+The buffer is not killed when currently visible or assigned to
+another tab."
+  (interactive)
+  (let ((to-kill (or (and name (get-buffer name)) (current-buffer)))
+        (side (or side sr-selected-window)))
+    (sr-tabs-remove to-kill side)
+    (if (and (not (memq to-kill (list sr-left-buffer sr-right-buffer)))
+             (not (member to-kill (apply 'append (mapcar 'cdr sr-tabs)))))
+        (kill-buffer to-kill))
+    (sr-tabs-refresh)))
+
+(defun sr-tabs-next (&optional n)
+  "Move focus to the next tab (left to right) in the active pane.
+With a prefix argument N, moves focus to the tab N places ahead,
+or to the last one if there are fewer tabs than requested."
+  (interactive "p")
+  (sr-tabs-step n))
+
+(defun sr-tabs-prev (&optional n)
+  "Move focus to the previous tab (right to left) in the active pane.
+With a prefix argument N, moves focus to the tab N places behind,
+or to the first one if there are fewer tabs than requested."
+  (interactive "p")
+  (sr-tabs-step n t))
+
+(defun sr-tabs-step (count &optional back)
+  "Move focus from the current tab to the one COUNT places ahead or behind.
+The direction depends on the value of BACK."
+  (let* ((stack (cdr (assq sr-selected-window sr-tabs)))
+         (stack (if back (reverse stack) stack))
+         (target (member (buffer-name) stack)))
+    (unless (null stack)
+      (if (or (null count) (zerop count))
+          (setq count 1))
+      (if (< 1 (length target))
+          (sr-tabs-switch-to-buffer (or (nth count target) (car (last target))))
+        (sr-tabs-switch-to-buffer (car stack))))))
+
+(defun sr-tabs-switch-to-buffer (to-buffer)
+  "Change context of the active Sunrise pane when switching buffers."
+  (let ((from-buffer (current-buffer))
+        (sr-current-path-faces
+         (with-current-buffer to-buffer sr-current-path-faces)))
+    (unless (eq from-buffer to-buffer)
+      (sr-save-aspect (switch-to-buffer to-buffer))
+      (setq sr-this-directory default-directory)
+      (set (sr-symbol sr-selected-window 'buffer) (current-buffer))
+      (set (sr-symbol sr-selected-window 'directory) default-directory)
+      (unless (eq from-buffer (sr-other 'buffer))
+        (with-current-buffer from-buffer
+          (set-buffer-modified-p nil)
+          (kill-buffer (current-buffer))))
+      (condition-case nil
+          (revert-buffer t t)
+        (error (ignore)))
+      (sr-history-push default-directory))
+    (sr-tabs-refresh)))
+
+(defun sr-tabs-focus (name side)
+  "Give focus to the tab with name NAME in SIDE pane."
+  (unless (eq side sr-selected-window)
+    (sr-change-window))
+  (sr-tabs-switch-to-buffer name))
+
+(defun sr-tabs-kill-and-go ()
+  "Kill the current Sunrise buffer and move to the next one.
+This kills the buffer, removes its assigned tab (if any) and
+moves to the next buffer tabbed in the active pane, unless there
+are no more tabbed buffers to fall back to, in which case just
+removes the tab."
+  (interactive)
+  (let ((to-kill (current-buffer))
+        (stack (cdr (assq sr-selected-window sr-tabs))))
+    (if (null stack)
+        (sr-kill-pane-buffer)
+      (sr-tabs-kill)
+      (setq stack (cdr stack))
+      (sr-tabs-next)
+      (unless (or (null stack)
+                  (eq to-kill (current-buffer))
+                  (eq to-kill (sr-other 'buffer)))
+        (kill-buffer to-kill)))))
+
+(defun sr-tabs-rename (&optional new-name)
+  (interactive "sRename current tab to: ")
+  (let* ((key (buffer-name))
+         (cache (assq sr-selected-window sr-tabs-labels-cache))
+         (label (cadr cache)))
+    (if label
+        (sr-tabs-redefine-label key new-name))))
+
+(defun sr-tabs-transpose ()
+  "Swap the sets of tabs from one pane to the other."
+  (interactive)
+  (cl-labels ((flip (side) (setcar side (cdr (assq (car side) sr-side-lookup)))))
+    (dolist (registry (list sr-tabs sr-tabs-labels-cache))
+      (mapc #'flip registry)))
+  (sr-in-other (sr-tabs-refresh))
+  (sr-tabs-refresh))
+
+(defadvice sr-transpose-panes (after sr-tabs-advice-sr-transpose-panes ())
+  "Synchronize the tabs with the panes if so required (see the variable
+`sr-tabs-follow-panes'). Activated in the function `sr-tabs-engage'."
+  (if sr-tabs-follow-panes (sr-tabs-transpose)))
+
+;;; ============================================================================
+;;; Graphical interface:
+
+(defun sr-tabs-focus-cmd (name side)
+  "Return a function to give focus to the named NAME in the SIDE pane."
+  (let ((selector (if (eq side (caar sr-tabs)) #'caar #'caadr)))
+    `(lambda ()
+       (interactive)
+       (sr-tabs-focus ,name (funcall ',selector sr-tabs)))))
+
+(defun sr-tabs-rename-cmd (name)
+  "Return a function to rename the tab named NAME in both panes."
+  `(lambda (&optional new-name)
+     (interactive "sRename tab to: ")
+     (sr-tabs-redefine-label ,name new-name)))
+
+(defun sr-tabs-kill-cmd (name side)
+  "Return a function to delete the tab named NAME in the SIDE pane."
+  (let ((selector (if (eq side (caar sr-tabs)) #'caar #'caadr)))
+    `(lambda ()
+       (interactive)
+       (if (eq sr-selected-window (funcall ',selector sr-tabs))
+           (sr-tabs-kill ,name)
+         (sr-in-other
+          (sr-tabs-kill ,name))))))
+
+(defsubst sr-tabs-propertize-tag (string face keymap)
+  "Propertize STRING with FACE and KEYMAP so it can be used as a tab tag."
+  (propertize string
+              'face face
+              'help-echo "mouse-1: select tab\n\mouse-2: rename tab\n\mouse-3: kill tab"
+              'local-map keymap))
+
+(defun sr-tabs-make-tag (name as-active &optional tag)
+  "Return a propertized string for decorating a tab named NAME.
+AS-ACTIVE determines whether to propertize it as an active or a
+passive tab (nil = passive, t = active). The optional argument
+TAG allows to provide a pretty name to label the tab."
+  (let ((tag (or tag name))
+        (side sr-selected-window)
+        (keymap (make-sparse-keymap)))
+    (if (< sr-tabs-max-tabsize (length tag))
+        (setq tag (concat (substring tag 0 sr-tabs-max-tabsize) "…")))
+    (setq tag (concat sr-tabs-sep tag sr-tabs-sep))
+    (define-key keymap [header-line mouse-1] (sr-tabs-focus-cmd name side))
+    (define-key keymap [header-line mouse-2] (sr-tabs-rename-cmd name))
+    (define-key keymap [header-line mouse-3] (sr-tabs-kill-cmd name side))
+    (if as-active
+        (sr-tabs-propertize-tag tag 'sr-tabs-active-face keymap)
+      (sr-tabs-propertize-tag tag 'sr-tabs-inactive-face keymap))))
+
+(defun sr-tabs-make-label (name &optional alias)
+  "Return a new label for decorating a tab named NAME.
+A label is a dotted pair of tags, for active and passive state.
+The new label is put in cache for later reuse. The optional
+argument ALIAS allows to provide a pretty name to label the tab."
+  (let* ((alias (or alias name))
+         (label (cons (sr-tabs-make-tag name t alias)
+                      (sr-tabs-make-tag name nil alias)))
+         (entry (list (cons name label)))
+         (cache (assq sr-selected-window sr-tabs-labels-cache)))
+    (setcdr cache (append (cdr cache) entry))
+    label))
+
+(defun sr-tabs-trim-label (label)
+  "Remove all properties and trailing whitespace from the given string."
+  (replace-regexp-in-string "^\\s-+\\|\\s-+$"
+                            ""
+                            (substring-no-properties label)))
+
+(defun sr-tabs-redefine-label (name alias)
+  "Change the name displayed on the tab with assigned buffer NAME to ALIAS.
+By default, a tab is named after its assigned buffer. This function allows to
+give tabs names that are more readable or simply easier to remember."
+  (let* ((alias (sr-tabs-trim-label (or alias ""))) (cache))
+    (when (string= "" alias)
+        (setq alias (buffer-name)))
+    (setq cache (assq sr-selected-window sr-tabs-labels-cache))
+    (setcdr cache (delq nil
+                        (mapcar (lambda(x)
+                                  (and (not (equal (car x) name)) x))
+                                (cdr cache))))
+    (sr-tabs-make-label name alias)
+    (sr-tabs-refresh)))
+
+(defun sr-tabs-get-tag (name is-active)
+  "Retrieve the cached tag for the tab named NAME in state IS-ACTIVE.
+nil = inactive, t = active. Creates new labels when needed."
+  (let* ((cache (assq sr-selected-window sr-tabs-labels-cache))
+         (label (cdr (assoc name (cdr cache)))))
+    (if (null label)
+        (setq label (sr-tabs-make-label name)))
+    (if (< sr-tabs-max-cache-length (length (cdr cache)))
+        (setcdr cache (cddr cache)))
+    (if is-active (car label) (cdr label))))
+
+(defun sr-tabs-make-line ()
+  "Assemble a new tab line from cached tags and put it in the line cache."
+  (if (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+      (let ((tab-set (cdr (assq sr-selected-window sr-tabs)))
+            (tab-line (if (or (cdar sr-tabs)
+                              (cdr (cadr sr-tabs))) "" nil))
+            (current-name (buffer-name)))
+        (mapc (lambda (x)
+                (let ((is-current (equal current-name x)))
+                  (setq tab-line (concat tab-line sr-tabs-sep
+                                         (sr-tabs-get-tag x is-current)))))
+              tab-set)
+        (setcdr (assq sr-selected-window sr-tabs-line-cache) tab-line)
+        tab-line)
+    nil))
+
+(defsubst sr-tabs-empty-p (line)
+  (or (null line) (string= "" line)))
+
+(defsubst sr-tabs-empty-mask (line)
+  (or (and (null line) "") line))
+
+(defsubst sr-tabs-empty-null (line)
+  (if (sr-tabs-empty-p line) nil line))
+
+(defun sr-nonempty-p (line-list)
+  "Return non-nil if LINE-LIST contains at least one non-nil element."
+  (or (not (sr-tabs-empty-p (car line-list)))
+      (and (cdr line-list) (sr-nonempty-p (cdr line-list)))))
+
+(defun sr-tabs-xor (list1 list2)
+  "Replacement for function `set-exclusive-or'.
+Used to avoid dependency on cl-seq.el."
+  (cond ((null list1) list2)
+        ((null list2) list1)
+        ((equal list1 list2) nil)
+        (t
+         (let (result)
+           (mapc (lambda (element)
+                   (if (member element result)
+                       (setq result (delete element result))
+                     (setq result (cons element result))))
+                 (append list1 list2))
+           result))))
+
+(defun sr-tabs-refresh ()
+  "Update `header-line-format' in both panes.
+Uses the line cache for the passive one, and assembles a new tab
+line for the active one. In the (corner) case when both panes
+contain the same buffer, glues together the tab lines with a
+``double bar'' separator."
+  (setq sr-tabs-mode sr-tabs-on)
+  (sr-tabs-make-line)
+  (let ((line-list (mapcar 'cdr sr-tabs-line-cache))
+        (same-buffer (eq sr-left-buffer sr-right-buffer)))
+    (if same-buffer
+        (setq header-line-format
+              (and (sr-nonempty-p line-list)
+                   (mapconcat 'concat line-list sr-tabs-ligature)))
+      (let ((other-buffer (sr-other 'buffer)))
+        (if (eq 'right sr-selected-window)
+            (setq line-list (nreverse line-list)))
+        (if (apply 'sr-tabs-xor (mapcar 'sr-tabs-empty-p line-list))
+            (setq line-list (mapcar 'sr-tabs-empty-mask line-list))
+          (setq line-list (mapcar 'sr-tabs-empty-null line-list)))
+
+        (setq header-line-format (car line-list))
+
+        (when (buffer-live-p other-buffer)
+          (with-current-buffer other-buffer
+            (setq header-line-format (cadr line-list)))))))
+  (force-window-update))
+
+;;; ============================================================================
+;;; Private interface:
+
+(defun sr-tabs-bury-all ()
+  "Bury all currently tabbed buffers."
+  (let ((all-buffers (apply 'append (mapcar 'cdr sr-tabs))))
+    (if all-buffers
+        (mapc 'bury-buffer all-buffers))))
+
+(defun sr-tabs-protect-buffer ()
+  "Protect the current buffer from being automatically disposed
+by Sunrise when moving to another directory (called from
+`kill-buffer-query-functions' hook.)"
+  (let ((tab-name (buffer-name)))
+    (not (or (member tab-name (car sr-tabs))
+             (member tab-name (cadr sr-tabs))))))
+
+(defun sr-tabs-engage ()
+  "Enable the Sunrise Tabs extension."
+  (setq sr-tabs-on t)
+  (add-hook 'sr-refresh-hook 'sr-tabs-refresh)
+  (add-hook 'sr-quit-hook 'sr-tabs-bury-all)
+  (add-hook 'kill-buffer-query-functions 'sr-tabs-protect-buffer)
+  (ad-activate 'sr-transpose-panes)
+  (ad-activate 'sr-editable-pane)
+  (sr-tabs-refresh))
+
+(defun sr-tabs-disengage ()
+  "Disable the Sunrise Tabs extension."
+  (setq sr-tabs-on nil)
+  (remove-hook 'sr-refresh-hook 'sr-tabs-refresh)
+  (remove-hook 'sr-quit-hook 'sr-tabs-bury-all)
+  (remove-hook 'kill-buffer-query-functions 'sr-tabs-protect-buffer)
+  (ad-deactivate 'sr-transpose-panes)
+  (ad-deactivate 'sr-editable-pane)
+  (setq header-line-format (default-value 'header-line-format))
+  (sr-in-other (setq header-line-format (default-value 'header-line-format))))
+
+;;; ============================================================================
+;;; User interface:
+
+(defvar sr-tabs-mode-map (make-sparse-keymap))
+(define-key sr-tabs-mode-map [(control ?j)] 'sr-tabs-add)
+(define-key sr-tabs-mode-map [(control ?k)] 'sr-tabs-remove)
+(define-key sr-tabs-mode-map "*\C-k" 'sr-tabs-clean)
+(define-key sr-tabs-mode-map [(control ?p)] 'sr-tabs-prev)
+(define-key sr-tabs-mode-map [(control ?n)] 'sr-tabs-next)
+(define-key sr-tabs-mode-map [(meta tab)] 'sr-tabs-next)
+
+(define-key sr-tabs-mode-map [(control meta ?j)]
+  (lambda () (interactive) (sr-in-other (sr-tabs-add))))
+(define-key sr-tabs-mode-map [(control meta ?k)]
+  (lambda () (interactive) (sr-in-other (call-interactively 'sr-tabs-remove))))
+(define-key sr-tabs-mode-map [(control meta ?p)]
+  (lambda () (interactive) (sr-in-other (sr-tabs-prev))))
+(define-key sr-tabs-mode-map [(control meta ?n)]
+  (lambda () (interactive) (sr-in-other (sr-tabs-next))))
+(define-key sr-tabs-mode-map [(control meta tab)]
+  (lambda () (interactive) (sr-in-other (sr-tabs-next))))
+(define-key sr-tabs-mode-map "*\C-\M-k"
+  (lambda () (interactive) (sr-in-other (sr-tabs-clean))))
+
+(define-key sr-tabs-mode-map "\C-xk" 'sr-tabs-kill-and-go)
+(define-key sr-tabs-mode-map "\M-T"  'sr-tabs-transpose)
+
+(define-minor-mode sr-tabs-mode
+  "Tabs support for the Sunrise Commander file manager.
+This minor mode provides the following keybindings:
+
+        C-j ........... Create new tab (or rename existing tab) in active pane.
+        C-k ........... Kill the tab of the current buffer in the active pane.
+        C-n ........... Move to the next tab in the active pane.
+        C-p ........... Move to the previous tab in the active pane.
+
+        C-M-j ......... Assign the current buffer to a tab in the passive pane.
+        C-M-k ......... Kill the tab of the current buffer in the passive pane.
+        C-M-n ......... Move to the next tab in the passive pane.
+        C-M-p ......... Move to the previous tab in the passive pane.
+
+        C-x k ......... Kill buffer and move to the next tabbed one (if any).
+"
+  nil nil sr-tabs-mode-map
+  (unless (memq major-mode '(sr-mode sr-virtual-mode sr-tree-mode))
+    (setq sr-tabs-mode nil)
+    (error "Sorry, this mode can be used only within the Sunrise Commander."))
+  (if sr-tabs-mode
+      (sr-tabs-engage)
+    (sr-tabs-disengage)))
+
+(defvar sr-tabs-editable-dired-map
+  (let ((map (make-sparse-keymap)))
+    (set-keymap-parent map sr-tabs-mode-map)
+    (define-key map "\C-n"  'dired-next-line)
+    (define-key map "\C-p"  'dired-previous-line)
+    (define-key map "\C-tn" 'sr-tabs-next)
+    (define-key map "\C-tp" 'sr-tabs-prev)
+    map)
+  "Keymap for managing tabs inside Editable Dired mode panes.")
+
+(defadvice sr-editable-pane (after sr-tabs-advice-sr-editable-pane ())
+  "Install `sr-tabs-editable-dired-map' when in Editable Dired mode."
+  (add-to-list 'minor-mode-overriding-map-alist
+               `(sr-tabs-mode . ,sr-tabs-editable-dired-map)))
+
+;;; ============================================================================
+;;; Bootstrap:
+
+(defun sr-tabs-menu-init ()
+  "Initialize the Sunrise Tabs extension menu."
+  (unless (lookup-key sr-mode-map [menu-bar Sunrise])
+    (define-key sr-mode-map [menu-bar Sunrise]
+      (cons "Sunrise" (make-sparse-keymap))))
+  (let ((menu-map (make-sparse-keymap "Tabs")))
+    (define-key sr-mode-map [menu-bar Sunrise tabs] (cons "Tabs" menu-map))
+    (define-key menu-map [help] '("Help" . (lambda ()
+                                             (interactive)
+                                             (describe-function 'sr-tabs-mode))))
+    (define-key menu-map [transpose] '("Transpose" . sr-tabs-transpose))
+    (define-key menu-map [kill]      '("Kill and go to next" . sr-tabs-kill-and-go))
+    (define-key menu-map [next]      '("Next"         . sr-tabs-next))
+    (define-key menu-map [prev]      '("Previous"     . sr-tabs-prev))
+    (define-key menu-map [remove]    '("Remove"       . sr-tabs-remove))
+    (define-key menu-map [add]       '("Add/Rename"   . sr-tabs-add))))
+(defun sr-tabs-start-once ()
+  "Bootstrap the tabs mode on the first execution of the Sunrise Commander,
+after module installation."
+  (sr-tabs-mode t)
+  (sr-tabs-menu-init)
+  (remove-hook 'sr-start-hook 'sr-tabs-start-once)
+  (unintern 'sr-tabs-menu-init obarray)
+  (unintern 'sr-tabs-start-once obarray))
+(add-hook 'sr-start-hook 'sr-tabs-start-once)
+
+;;; ============================================================================
+;;; Desktop support:
+
+(defun sr-tabs-desktop-save-buffer (_desktop-dir)
+  "Return additional desktop data for saving tabs of the current Sunrise buffer."
+  (let* ((left-tab (car (member (buffer-name) (assoc 'left sr-tabs))))
+         (left-cache (cdr (assq 'left sr-tabs-labels-cache)))
+         (left-label (cadr (assoc left-tab left-cache)))
+         (right-tab (car (member (buffer-name) (assoc 'right sr-tabs))))
+         (right-cache (cdr (assq 'right sr-tabs-labels-cache)))
+         (right-label (cadr (assoc right-tab right-cache))))
+    (delq
+     nil
+     (list
+      (if left-label (cons 'left-tab (sr-tabs-trim-label left-label)))
+      (if right-label (cons 'right-tab (sr-tabs-trim-label right-label)))))))
+
+(defun sr-tabs-desktop-restore-buffer (_desktop-buffer-file-name
+                                       _desktop-buffer-name
+                                       desktop-buffer-misc)
+  "Restore all tabs in a Sunrise (normal or VIRTUAL) buffer from a desktop file."
+  (mapc (lambda (side)
+          (let* ((sr-selected-window side)
+                 (tab-symbol (intern (concat (symbol-name side) "-tab")))
+                 (name (buffer-name))
+                 (label (cdr (assq tab-symbol desktop-buffer-misc)))
+                 (tab-set (assq side sr-tabs)))
+            (when label
+              (setcdr tab-set (cons name (cdr tab-set)))
+              (sr-tabs-make-label name label))))
+        '(left right))
+  (unless sr-tabs-on
+    (sr-tabs-engage)))
+
+(defun sr-tabs-reset-state ()
+  "Reset some environment variables that control the behavior of
+tabs in the Sunrise Commander (used for desktop support)."
+  (mapc (lambda (x) (setcdr x nil)) sr-tabs-labels-cache)
+  (mapc (lambda (x) (setcdr x nil)) sr-tabs)
+  nil)
+
+;; These append the previous functions to the generic desktop support in Sunrise:
+(add-to-list 'sr-desktop-save-handlers 'sr-tabs-desktop-save-buffer)
+(add-to-list 'sr-desktop-restore-handlers 'sr-tabs-desktop-restore-buffer)
+
+;; This activates the tabs support after desktop restoration:
+(add-hook
+ 'desktop-after-read-hook
+ (defun sr-tabs-desktop-after-read-function ()
+   (unless (assq 'sr-tabs-on desktop-globals-to-clear)
+     (add-to-list 'desktop-globals-to-clear
+                  '(sr-tabs-on . (sr-tabs-reset-state))))))
+
+(defun sunrise-x-tabs-unload-function ()
+  (sr-ad-disable "^sr-tabs-"))
+
+(provide 'sunrise-x-tabs)
+
+;;;###autoload (eval-after-load 'sunrise-commander '(sr-extend-with 'sunrise-x-tabs))
+
+;;; sunrise-x-tabs.el ends here
diff --git a/elisp/sunrise/sunrise-x-tree.el b/elisp/sunrise/sunrise-x-tree.el
new file mode 100644 (file)
index 0000000..c858d51
--- /dev/null
@@ -0,0 +1,1213 @@
+;;; sunrise-x-tree.el --- Tree View for the Sunrise Commander File Manager -*- lexical-binding: t -*-
+
+;; Copyright (C) 2010-2012 José Alfredo Romero Latouche.
+
+;; Author: José Alfredo Romero L. <escherdragon@gmail.com>
+;;     Štěpán Němec <stepnem@gmail.com>
+;; Maintainer: José Alfredo Romero L. <escherdragon@gmail.com>
+;; Created: 4 May 2010
+;; Version: 1
+;; RCS Version: $Rev: 440 $
+;; Keywords: sunrise commander, directories tree navigation
+;; URL: http://www.emacswiki.org/emacs/sunrise-x-tree.el
+;; Compatibility: GNU Emacs 22+
+
+;; This file is not part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify it under
+;; the terms of the GNU General Public License as published by the Free Software
+;; Foundation, either version 3 of the License, or (at your option) any later
+;; version.
+;;
+;; This program is distributed in the hope that it will be useful, but WITHOUT
+;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more de-
+;; tails.
+
+;; You should have received a copy of the GNU General Public License along with
+;; this program. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; This extension adds to the Sunrise Commander file manager a directories-only
+;; tree view that can be used for fast navigation, as well as for several basic
+;; operations on files and directories. It uses the excellent "tree-widget.el"
+;; library written by David Ponce and works the same in text consoles as well as
+;; in graphical environments, using either the mouse or just the keyboard.
+
+;; For more information on the Sunrise Commander, other extensions and cool tips
+;; & tricks visit http://www.emacswiki.org/emacs/Sunrise_Commander
+
+;; This extension was developed on GNU Emacs 24 on Linux and tested on GNU Emacs
+;; 22 and 24 for Linux, and on EmacsW32 (version 23) for Windows.
+
+;;; Installation:
+
+;; 1) Put this file somewhere in your Emacs `load-path'.
+
+;; 2) Add a (require 'sunrise‐x‐tree) expression to your .emacs file somewhere
+;; after the (require 'sunrise‐commander) one.
+
+;; 3) Evaluate the new expression, or reload your .emacs file, or restart Emacs.
+
+;; 4) You may have to customize the `tree-widget-image-enable' variable if all
+;; you get are text-only icons (e.g. "[+]" and "[X]") in your graphical
+;; environment, while you'd rather prefer looking at pretty graphical ones.
+
+;; WARNING: If you use Slime be aware that some versions of this package include
+;; an older version of tree-widget.el that may clobber the one in Emacs and make
+;; this extension work improperly. At least that's the case in Debian for i386:
+;; slime comes with version 21.4 of tree-widget, but the extension requires 22.1
+;; or better.
+
+;;; Usage:
+
+;; In order to explain the different ways this extension is used, it's necessary
+;; to first introduce a few concepts:
+;; * A Sunrise Tree View pane displays a list of directories arranged in a tree-
+;;   like structure. There is exactly one such TREE in every Tree View pane.
+;; * Each node in this tree is called FOLDER and represents one directory in the
+;;   file system. A folder can be in one of two states: OPEN or CLOSED. When the
+;;   folder is open, its children (if any) are displayed under it in the tree.
+;; * The top-most folder in every tree is called the ROOT of the tree. All other
+;;   folders in the same tree represent sub-directories of the root directory.
+;; * To FOCUS a given folder means to replace the current tree with one that has
+;;   that folder as its root.
+;; * The opposite operation of focusing a folder, i.e. showing it in the context
+;;   of a broader tree, is called BLURRING the folder.
+;; * Finally, to EXPLODE a given folder means to open it, then all its children,
+;;   then all the children of its children and so on, as many times as the value
+;;   of  the   `sr-tree-explosion-ratio'  option   (which  can   be  customized)
+;;   specifies. This  is an additive  operation, which means that  exploding the
+;;   same directory  many times  will open  more of  its descendants  deeper and
+;;   deeper until the tree runs out of closed folders in that branch.
+
+;; The Sunrise Tree View mode offers three different ways of navigating the file
+;; system: with the mouse (for rodent lovers), with the arrow keys (for rookies)
+;; and with other keys nearer the home row (for keyboard junkies).
+
+;; 1. With the mouse:
+
+;; * Meta + Shift + left click anywhere inside a pane to switch between tree and
+;;   normal modes.
+;; * Left click on a folder or anywhere beside it to open or close the folder.
+;; * Middle click on a folder, or anywhere beside it, to just select it without
+;;   changing its state.
+;; * Shift + left click on a folder or anywhere beside it to focus it.
+;; * Meta + left click on a folder or anywhere beside it to blur it. Meta + left
+;;   click anywhere else in the pane to blur the currently selected folder.
+;; * Control + left click on a folder or anywhere beside it to explode it.
+;; * Double click on a folder to dismiss tree view and visit the folder.
+;; * Left or Middle click anywhere on the path line at the top of the pane to go
+;;   directly to the directory which path ends at that point in the line.
+
+;; 2. With the arrow keys:
+
+;; * Meta + Shift + down switches between tree and normal modes.
+;; * Up and down move the cursor up and down (duh!)
+;; * Right opens a folder if it was closed, or browses it if it was open.
+;; * Left closes a folder if it was open, or jumps up to its parent folder if it
+;;   was closed.
+;; * Shift + right focuses the selected folder.
+;; * Shift + left blurs the selected folder.
+;; * Control + right explodes the selected folder.
+;; * If you're in a text console and  the bindings above don't work for you, try
+;;   using Escape instead of Shift (not combined -- first press escape, then the
+;;   arrow key) and C-c instead of Control.
+
+;; 3. With alphanumeric keys:
+
+;; * C-t Space (alternatively C-t Return) - switch between modes.
+;; * n, p - move cursor up/down.
+;; * Space, Return - open closed folder / browse already open folder.
+;; * Backspace - close open folder / jump to parent of already closed folder.
+;; * C-c f - focus the selected folder.
+;; * C-c b - blur the selected folder.
+;; * C-Return, C-c Return - explode the selected folder.
+;; * f - browse the selected folder in normal mode.
+;; * v, o - view the selected folder in the passive pane, in whatever mode it
+;;   happens to be at that moment.
+;; * C-c C-c - dismiss tree view and return to normal mode.
+
+;; * C-q is simply another binding for the usual pane synchronization (C-c C-z)
+;;   already present in Sunrise Commander Core, which in tree mode performs the
+;;   "Quick View" operation required by the OFM standard.
+
+;; * C-u C-s, C-u C-r - "sticky" interactive search. This works like the regular
+;; isearch, but when the current search is finished with a Return, the folder
+;; the cursor ends on is automatically opened and a new (forward) Isearch
+;; starts, so one can continue searching among the children of that folder. This
+;; allows for extremely fast navigation across lengthy paths of directories with
+;; just a few keystrokes. To terminate a sticky search, press C-g or (once
+;; again) Return. Sticky searches can be made default in tree panes by
+;; customizing the variable `sr-tree-isearch-always-sticky' - when set, prefix
+;; the command to start a regular (non-sticky) interactive search.
+
+;; * When AVFS support is active, press "#" to toggle the display of compressed
+;; archives in Tree View panes.
+
+;; Additionally, most of the original keybindings from Sunrise apply (wherever
+;; it makes sense, of course). For instance switching/transposing/laying out
+;; panes (Tab, M-Tab, C-c, C-s), showing / hiding hidden directories (C-o),
+;; jumping to parent/arbitrary directory (J, j) and many more, including the
+;; following file manipulation commands: copy (C), clone (K), rename (R), delete
+;; (D), symlink (S), relative symlink (Y), create a new directory (+) and show
+;; file size (y).
+
+;; All directory commands from the Sunrise Buttons extension are also supported.
+;; It is required to upgrade the Buttons extension to version 1R293 or better to
+;; make this integration work correctly, though.
+
+;; Hey, and don't forget to enjoy ;-)
+
+;;; Code:
+
+(require 'sunrise-commander)
+(require 'tree-widget)
+(require 'hl-line)
+(eval-when-compile (require 'desktop))
+
+(defcustom sr-tree-explosion-ratio 3
+  "Maximum number of directory levels to recursively open at a time.
+Used by the command `sr-tree-explode-branch'."
+  :group 'sunrise
+  :type 'integer)
+
+(defcustom sr-tree-isearch-always-sticky nil
+  "Whether interactive searches are always sticky in tree panes."
+  :group 'sunrise
+  :type 'boolean)
+
+(defcustom sr-tree-avfs-handlers-alist '(("\\.od[fgpst]$" . "#uzip/")
+                                         ("\\.oxt$"       . "#uzip/")
+                                         ("\\.sx[dmicw]$" . "#uzip/"))
+  "List of AVFS handlers to manage specific file extensions in Tree View mode."
+  :group 'sunrise
+  :type 'alist)
+
+(defvar sr-tree-root nil
+  "Root widget of the current tree view.")
+
+(defvar sr-tree-open-paths nil
+  "List of paths to all the directories open in the current tree view.")
+
+(defvar sr-tree-avfs-seen nil
+  "List of paths to big compressed archives visited through AVFS.")
+
+(defvar sr-tree-cursor nil
+  "Cons cell of the from (LABEL . FILEPATH).
+FILEPATH is the path to the selected directory in the current tree
+view. LABEL is the name displayed in the tree representing FILEPATH")
+
+(defvar sr-tree-mode-map (make-sparse-keymap)
+  "Keymap for the Sunrise Commander Tree View.")
+
+(defvar sr-buttons-command-adapter nil
+  "Compiler pacifier.
+See `sr-buttons-command-adapter' in sunrise-x-buttons.el.")
+
+(defvar sr-tree-omit-archives t "")
+
+(define-widget 'sr-tree-dir-widget 'tree-widget
+  "Directory Tree widget."
+  :dynargs  'sr-tree-expand-dir
+  :has-children   t)
+
+;;; ============================================================================
+;;; GUI Management functions:
+
+(defun sr-tree-focus-widget ()
+  "Move point to the first button widget in the current line (if any)."
+  (interactive)
+  (beginning-of-line)
+  (unless (get-char-property (point) 'button)
+    (while (not (or (eolp) (get-char-property (point) 'button)))
+      (forward-char))))
+
+(defun sr-tree-get-button ()
+  "Return the first button widget in the current line (if any)."
+  (sr-tree-focus-widget)
+  (get-char-property (point) 'button))
+
+(defun sr-tree-get-branch ()
+  "Return the first tree widget in the current line (if any)."
+  (widget-get (sr-tree-get-button) :parent))
+
+(defun sr-tree-get-cursor ()
+  "Return a cursor as documented in `sr-tree-cursor'."
+  (let* ((cursor-node (sr-tree-get-button))
+         (cursor-tree (if cursor-node (widget-get cursor-node :parent)))
+         (cursor-node (widget-get cursor-node :node))
+         (cursor-tag (widget-get cursor-node :tag))
+         (cursor-path (sr-tree-path-line (widget-get cursor-tree :path))))
+    (and cursor-tag cursor-path (cons cursor-tag cursor-path))))
+
+(defun sr-tree-update-cursor ()
+  "Update the cursor (cf. `sr-tree-cursor').
+Also updates other graphical elements of the interface, depending
+on the position of the point."
+  (setq sr-tree-cursor (sr-tree-get-cursor))
+  (when sr-tree-cursor
+    (setq sr-this-directory (cdr sr-tree-cursor))
+    (sr-tree-highlight)
+    (setq default-directory (file-name-as-directory (cdr sr-tree-cursor)))
+    (when (and (featurep 'sunrise-x-modeline)
+               (not (eq mode-line-format (default-value 'mode-line-format))))
+      (if (fboundp 'sr-modeline-refresh)
+          (sr-modeline-refresh))
+      (force-mode-line-update))
+    (if (and sr-synchronized
+             (not (eq sr-left-buffer sr-right-buffer))
+             (eq (selected-window) (sr-this 'window)))
+        (sr-tree-advertised-find-file-other))))
+
+(defun sr-tree-refresh-dir (widget &rest _ignore)
+  "Refresh WIDGET parent (or own) tree children. IGNORE other arguments."
+  (let ((tree (if (tree-widget-p widget)
+                  widget
+                (widget-get widget :parent))))
+    (widget-put tree :args nil) ;; Clear the tree children cache.
+    (widget-value-set tree (widget-value tree))) ;; Redraw the tree node.
+  (if (fboundp 'sr-tabs-refresh)
+      (sr-tabs-refresh))
+  (sr-highlight))
+
+(defun sr-tree-refresh-branch (&optional prefix)
+  "Revert the currently selected branch in the directory tree.
+If no branch is selected, then select the root node and revert
+the whole tree. If PREFIX is non-nil, close all open
+subdirectories in the tree first."
+  (interactive "P")
+  (if prefix
+      (setq sr-tree-open-paths nil))
+  (let ((button (sr-tree-get-button)))
+    (unless button
+      (sr-tree-beginning-of-buffer)
+      (setq button (sr-tree-get-button)))
+    (sr-tree-refresh-dir button)))
+
+(defun sr-tree-revert-buffer (&optional _ignore-auto _noconfirm)
+  "Revert the current Sunrise Tree View buffer."
+  (interactive)
+  (sr-tree-refresh-branch))
+
+(defun sr-tree-widget (e &optional open)
+  "Return a widget to display directory E.
+With a non-nil optional argument OPEN, display the widget as open
+initially."
+  (let ((is-open (or open (member e sr-tree-open-paths)))
+        (tag (sr-chop ?/ e)))
+    (setq tag (file-name-as-directory (file-name-nondirectory tag)))
+    `(sr-tree-dir-widget
+      :open ,is-open
+      :node (push-button
+             :tag ,tag
+             :format "%[%t%]\n"
+             :notify sr-tree-refresh-dir)
+      :path ,e)))
+
+(defun sr-tree-path-line (&optional path)
+  "Transform PATH into a suitable path line for displaying at the pane top."
+  (sr-chop ?/ (expand-file-name (or path (cdr sr-tree-cursor) ""))))
+
+(defun sr-tree-highlight ()
+  "Set up the path line in the current Sunrise Tree buffer."
+  (save-excursion
+    (let ((inhibit-read-only t))
+      (goto-char (point-min))
+      (delete-region (point) (line-end-position))
+      (widget-insert (propertize (concat (sr-tree-path-line nil) " ")
+                                 'face 'sr-passive-path-face))
+      (forward-line 1)
+      (delete-region (line-beginning-position) (line-end-position))
+      (widget-insert
+       (format "%s" (if sr-tree-omit-archives "" "virtual directories: ON ")))
+      (sr-highlight))))
+
+(defun sr-tree-check-virtual-size (entry)
+  "Allow user to abort before trying to access a large archive through AVFS."
+  ;; TODO: use function abort-if-file-too-large instead:
+  (if (and sr-avfs-root
+           (sr-overlapping-paths-p sr-avfs-root entry)
+           (string-match "^\\([^#]+\\)#" entry))
+      (let* ((root (match-string 1 entry))
+             (root (substring root (length sr-avfs-root)))
+             (size (nth 7 (file-attributes root))))
+        (when (and large-file-warning-threshold
+                   (not (member root sr-tree-avfs-seen))
+                   (> size large-file-warning-threshold))
+          (or (y-or-n-p
+               (format "File %s is large (%dMB), really open? "
+                       (file-name-nondirectory root) (/ size 1048576)))
+              (error "Aborted"))
+          (sr-tree-avfs-register-seen root)))))
+
+(defun sr-tree-list (dir)
+  "Return the list of subdirectories in DIR."
+  (let ((entries (directory-files dir 'full)) dirs entry rel-entry)
+    (while entries
+      (setq entry (car entries)
+            rel-entry (file-relative-name entry (concat entry "/.."))
+            entries (cdr entries))
+
+      (cond ((eq ?. (string-to-char (substring entry -1)))
+             (ignore))
+
+            ((and dired-omit-mode (eq ?. (string-to-char rel-entry)))
+             (ignore))
+
+            ((file-directory-p entry)
+             (setq dirs (cons entry dirs)))
+
+            ((and (not sr-tree-omit-archives) (sr-avfs-directory-p entry))
+             (setq dirs (cons (sr-tree-avfs-dir entry) dirs)))
+
+            (t (ignore))))
+    (nreverse dirs)))
+
+(defun sr-tree-avfs-dir (filename)
+  "Return the virtual path for accessing FILENAME through AVFS in Tree View panes.
+Returns nil if AVFS cannot manage this kind of file."
+  (let* ((handler
+          (or (assoc-default filename sr-tree-avfs-handlers-alist 'string-match)
+              (assoc-default filename sr-avfs-handlers-alist 'string-match)))
+          (vdir (concat filename handler)))
+    (unless (sr-overlapping-paths-p sr-avfs-root vdir)
+      (setq vdir (concat sr-avfs-root vdir)))
+    (sr-tree-path-line vdir)))
+
+(defun sr-tree-expand-dir (tree)
+  "Return TREE widget children. Reuse :args cache if it exists."
+  (or (widget-get tree :args)
+      (let ((dir (widget-get tree :path)))
+        (message "Reading directory '%s'..." dir)
+        (condition-case err
+            (prog1
+                (mapcar 'sr-tree-widget (sr-tree-list dir))
+              (message "Reading directory '%s'...done" dir))
+          (error
+           (widget-put tree :open nil)
+           (message "%s" (error-message-string err))
+           nil)))))
+
+(defun sr-tree-register-path (widget)
+  "Add path from WIDGET to the current Sunrise Tree buffer's list of open paths."
+  (let ((path (sr-tree-path-line (widget-get widget :path))))
+    (setq sr-tree-open-paths
+          (if (widget-get widget :open)
+              (cons path sr-tree-open-paths)
+            (delete path sr-tree-open-paths)))))
+(add-hook 'tree-widget-after-toggle-functions 'sr-tree-register-path)
+
+(defun sr-tree-avfs-register-seen (path)
+  "Add the PATH to the list of (big) archives visited through AVFS."
+  (setq sr-tree-avfs-seen (cons path (delete path sr-tree-avfs-seen))))
+
+(defun sr-tree-build (root)
+  "Delete the current tree widget and build a new one at ROOT."
+  (interactive "DSunrise Tree Root: ")
+  (setq default-directory
+        (file-name-as-directory (setq root (expand-file-name root))))
+  (let ((inhibit-read-only t)
+        (all (overlay-lists)))
+    (erase-buffer)
+    (mapc 'delete-overlay (car all))
+    (mapc 'delete-overlay (cdr all))
+    (tree-widget-set-theme "folder")
+    (widget-insert (format "%s\n\n" (sr-tree-path-line root)))
+    (set
+     (make-local-variable 'sr-tree-root)
+     (widget-create (sr-tree-widget root t)))
+    (widget-setup)
+    (if sr-tree-cursor
+        (sr-tree-search-cursor)
+      (sr-tree-beginning-of-buffer))
+    (sr-tree-refresh-branch)
+    (sr-tree-register-path sr-tree-root)))
+
+(defun sr-tree-build-new (root)
+  "Build a new Tree View pane in a new buffer at ROOT."
+  (interactive "DSunrise Tree Root: ")
+  (let ((default-directory root))
+    (sr-save-aspect
+     (sr-alternate-buffer
+      (switch-to-buffer (generate-new-buffer "Sunrise Tree")))
+     (sr-tree-mode))))
+
+(defun sr-tree-goto-dir (root &optional keep-state)
+  "`sr-goto-dir' replacement for buffers in Sunrise Tree mode.
+See also the variable `sr-goto-dir-function'."
+  (interactive)
+  (setq root (expand-file-name root))
+  (let ((cursor sr-tree-cursor)
+        (open-paths sr-tree-open-paths))
+    (sr-tree-build-new root)
+    (when keep-state
+        (setq sr-tree-cursor cursor
+              sr-tree-open-paths (mapcar 'identity open-paths))))
+  (sr-keep-buffer)
+  (sr-history-push root))
+
+(defadvice sr-focus-filename
+  (around sr-tree-advice-sr-focus-filename (filename))
+  "Force deactivation of Sunrise Tree View before focusing a regular file."
+  (if (eq major-mode 'sr-tree-mode)
+      (if (file-directory-p filename)
+          (let* ((path (directory-file-name (expand-file-name filename)))
+                 (label (file-name-as-directory (file-name-nondirectory path))))
+            (with-no-warnings (sr-tree-search-cursor (cons label path))))
+        (with-no-warnings (sr-tree-dismiss))
+        ad-do-it)
+    ad-do-it))
+(ad-activate 'sr-focus-filename)
+
+;;; ============================================================================
+;;; GUI interaction functions:
+
+(defun sr-tree-next-line ()
+  "Move point to the next line in the current pane."
+  (interactive)
+  (forward-line)
+  (sr-tree-update-cursor))
+
+(defun sr-tree-previous-line ()
+  "Move point to the previous line in the current pane."
+  (interactive)
+  (forward-line -1)
+  (sr-tree-update-cursor))
+
+(defun sr-tree-scroll-down (&optional arg)
+  "Scroll down the current pane without moving the point (if possible)."
+  (interactive)
+  (scroll-down arg)
+  (sr-tree-update-cursor))
+
+(defun sr-tree-scroll-up (&optional arg)
+  "Scroll up the current pane without moving the point (if possible)."
+  (interactive)
+  (scroll-up arg)
+  (sr-tree-update-cursor))
+
+(defun sr-tree-beginning-of-buffer ()
+  "Move cursor to the top of the current Sunrise Tree View pane."
+  (interactive)
+  (goto-char (widget-get sr-tree-root :from))
+  (sr-tree-update-cursor))
+
+(defun sr-tree-end-of-buffer ()
+  "Move cursor to the bottom of the current Sunrise Tree View pane."
+  (interactive)
+  (forward-sentence)
+  (sr-tree-update-cursor))
+
+(defun sr-tree-toggle-branch (&optional action)
+  "Open/close (graphically) the node selected in the current Sunrise Tree pane.
+Optional ACTION is one of the symbols `open' or `close' and
+allows to specify whether the node has to be open only if closed,
+or closed only if open."
+  (interactive)
+  (let* ((branch (sr-tree-get-branch))
+         (is-open (widget-get branch :open))
+         (path))
+    (unless is-open
+      (setq path (widget-get branch :path))
+      (sr-tree-check-virtual-size path)
+      t)
+    (when (or (and is-open (eq action 'close))
+              (and (not is-open) (eq action 'open))
+              (null action))
+      (sr-tree-focus-widget)
+      (widget-button-press (point))
+      t)))
+
+(defun sr-tree-open-branch ()
+  "Unfold (graphically) the directory selected in the current Sunrise Tree pane.
+Displays the subdirectories directly under it."
+  (interactive)
+  (if (widget-get (sr-tree-get-branch) :open)
+      (sr-tree-advertised-find-file)
+    (sr-tree-toggle-branch 'open)))
+
+(defun sr-tree-close-branch ()
+  "Fold the selected directory.
+Hides all subdirectories being displayed under it or any of its
+subdirectories."
+  (interactive)
+  (sr-tree-toggle-branch 'close))
+
+(defun sr-tree-collapse-branch ()
+  "If the current folder is open, close it.
+If it is closed, move to its parent directory, building a new
+tree if necessary."
+  (interactive)
+  (let ((branch (sr-tree-get-branch)))
+    (if (widget-get branch :open)
+        (sr-tree-close-branch)
+      (sr-tree-prev-subdir)
+      (unless (eq (sr-tree-get-branch) sr-tree-root)
+        (sr-tree-close-branch)))))
+
+(defun sr-tree-explode-branch (&optional level branch)
+  "Open the selected directory and all its open subdirectories recursively.
+The number of levels is determined by the variable
+`sr-tree-explosion-ratio'. LEVEL and BRANCH optional arguments
+are used only internally to control recursion."
+  (interactive)
+  (unless (or level branch)
+    (recenter (truncate (/ (window-body-height) 10.0))))
+  (let ((level (or level sr-tree-explosion-ratio))
+        (branch (or branch (sr-tree-get-branch))))
+    (when (and branch (< 0 level))
+      (unless (widget-get branch :open)
+        (setq level (1- level))
+        (funcall #'tree-widget-action branch))
+      (dolist (child (cdr (widget-get branch :children)))
+        (sr-tree-explode-branch level child)))))
+
+(defun sr-tree-search-cursor (&optional init-cursor recursing)
+  "Try to move the point to the node represented by INIT-CURSOR.
+If it is nil, use the value of `sr-tree-cursor' instead. On
+failure, put the point at the top of the pane."
+  (let ((cursor (or init-cursor sr-tree-cursor)) new-cursor)
+    (if (null cursor)
+        (sr-tree-beginning-of-buffer)
+      (unless recursing (goto-char (point-min)))
+      (when (search-forward (car cursor) (point-max) t)
+        (setq new-cursor (sr-tree-get-cursor))
+        (if (and new-cursor (not (sr-equal-dirs (cdr cursor) (cdr new-cursor))))
+            (progn
+              (sr-tree-next-line)
+              (sr-tree-search-cursor cursor t))
+          (sr-tree-update-cursor))))))
+
+(defun sr-tree-isearch-prompt ()
+  "Display the message that appears when a sticky search is launched."
+  (message (propertize "Sunrise Tree sticky I-search (C-g to exit): "
+                       'face 'minibuffer-prompt)))
+
+(defvar sr-tree-isearch-mode-commands
+  '(([S-return]  . 'sr-tree-focus-branch)
+    ([S-right]   . 'sr-tree-focus-branch)
+    ([?\e right] . 'sr-tree-focus-branch)
+    ("\C-cf"     . 'sr-tree-focus-branch)
+
+    ([M-return]  . 'sr-tree-blur-branch)
+    ([S-left]    . 'sr-tree-blur-branch)
+    ([?\e left]  . 'sr-tree-blur-branch)
+    ("\C-cb"     . 'sr-tree-blur-branch)
+
+    ([C-return]  . 'sr-tree-explode-branch)
+    ([C-right]   . 'sr-tree-explode-branch)
+    ([3 right]   . 'sr-tree-explode-branch)
+    ("\C-c\C-m"  . 'sr-tree-explode-branch)
+
+    ) "Keybindings installed in `isearch-mode' during a sticky search.")
+
+(defsubst sr-tree-isearch-command (binding)
+  `(lambda () (interactive) (sr-tree-post-isearch ,(cdr binding))))
+
+(defun sr-tree-isearch-setup ()
+  "Set up Isearch to perform sticky searches in Sunrise Tree panes.
+Used from `isearch-mode-hook'."
+  (add-hook 'isearch-mode-end-hook 'sr-tree-post-isearch)
+  (set (make-local-variable 'search-nonincremental-instead) nil)
+  (define-key isearch-mode-map "\C-c" (make-sparse-keymap))
+  (mapc (lambda (binding)
+          (define-key
+            isearch-mode-map
+            (car binding) (sr-tree-isearch-command binding)))
+        sr-tree-isearch-mode-commands)
+  (run-hooks 'sr-refresh-hook))
+
+(defun sr-tree-isearch-done ()
+  "Clean up the Isearch hook and keymap after a sticky search."
+  (remove-hook 'isearch-mode-end-hook 'sr-tree-post-isearch)
+  (kill-local-variable 'search-nonincremental-instead)
+  (mapc (lambda (binding)
+          (define-key isearch-mode-map (car binding) nil))
+        sr-tree-isearch-mode-commands)
+  (define-key isearch-mode-map "\C-c" 'isearch-other-control-char)
+  (isearch-done)
+  (setq isearch-mode-end-hook-quit t))
+
+(defun sr-tree-isearch-forward (&optional prefix)
+  "Prefixable version of `isearch-forward' used in Sunrise Tree mode.
+With PREFIX, starts a new `isearch-forward' immediately after the
+previous one exits as long as C-g is not pressed."
+  (interactive "P")
+  (if (or (and prefix (not sr-tree-isearch-always-sticky))
+          (and (not prefix) sr-tree-isearch-always-sticky))
+      (sr-tree-sticky-isearch-forward)
+    (isearch-forward nil t)))
+
+(defun sr-tree-sticky-isearch-forward ()
+  "Chain Isearch operations to allow fast navigation through long file paths.
+Press C-g to abort, or Return twice on a folder to dismiss Tree
+View and visit that folder."
+  (interactive)
+  (sr-tree-isearch-setup)
+  (isearch-forward nil t)
+  (run-with-idle-timer 0.01 nil 'sr-tree-isearch-prompt))
+
+(defun sr-tree-isearch-backward (&optional prefix)
+  "Prefixable version of `isearch-backward' used in Sunrise Tree mode.
+With PREFIX, starts a new `isearch-forward' immediately after the
+previous search exits until C-g is pressed."
+  (interactive "P")
+  (if (or (and prefix (not sr-tree-isearch-always-sticky))
+          (and (not prefix) sr-tree-isearch-always-sticky))
+      (sr-tree-isearch-setup))
+  (isearch-backward nil t)
+  (run-with-idle-timer 0.01 nil 'sr-tree-isearch-prompt))
+
+(defun sr-tree-sticky-isearch-backward ()
+  "Chain Isearch operations to allow fast navigation through long file paths.
+Press C-g to abort, or Return twice on a folder to dismiss Tree
+View and visit that folder."
+  (interactive)
+  (sr-tree-isearch-setup)
+  (isearch-backward nil t))
+
+(defun sr-tree-post-isearch (&optional command)
+  "Installed in `isearch-mode-end-hook' during sticky Isearch operations."
+  (sr-tree-update-cursor)
+  (cond (command (sr-tree-isearch-command-loop command))
+        (isearch-mode-end-hook-quit (sr-tree-isearch-done))
+        ((equal "" isearch-string) (sr-tree-open-branch))
+        ((and (sr-tree-toggle-branch 'open)
+              (widget-get (sr-tree-get-branch) :args))
+         (recenter (truncate (/ (window-body-height) 10.0))))
+        (t (ignore)))
+  (unless isearch-mode-end-hook-quit
+    (sr-tree-sticky-isearch-forward)))
+
+(defun sr-tree-isearch-command-loop (command)
+  (funcall command)
+  (let* ((msg "Sunrise Tree: sticky Isearch (C-g to exit)")
+         (key (read-key-sequence msg))
+         (next-command (lookup-key sr-tree-mode-map key)))
+    (while (memq next-command '(sr-tree-explode-branch
+                                sr-tree-focus-branch
+                                sr-tree-blur-branch))
+      (funcall next-command)
+      (setq key (read-key-sequence msg)
+            next-command (lookup-key sr-tree-mode-map key)))
+    (isearch-unread-key-sequence (listify-key-sequence key))
+    (setq isearch-mode-end-hook-quit nil)))
+
+(defun sr-tree-focus-branch ()
+  "Replace the current tree with a new one rooted in the selected directory."
+  (interactive)
+  (unless (eq (sr-tree-get-branch) sr-tree-root)
+    (sr-tree-goto-dir (cdr sr-tree-cursor) t)
+    (if sr-tree-open-paths (revert-buffer))))
+
+(defun sr-tree-blur-branch ()
+  "Change root of the current tree to its parent, keeping the cursor position."
+  (interactive)
+  (let ((cursor sr-tree-cursor))
+    (unless (eq (sr-tree-get-branch) sr-tree-root)
+      (sr-tree-beginning-of-buffer))
+    (sr-tree-prev-subdir t)
+    (revert-buffer)
+    (sr-tree-search-cursor cursor))
+  (recenter))
+
+(defun sr-tree-omit-mode (&optional force)
+  "Toggle `dired-omit-mode' in the current Sunrise Tree View pane."
+  (interactive)
+  (setq dired-omit-mode (or force (not dired-omit-mode)))
+  (revert-buffer))
+
+(defun sr-tree-avfs-toggle ()
+  "Toggle display of compressed archives in Sunrise Tree View panes."
+  (interactive)
+  (if sr-avfs-root
+      (let ((cursor sr-tree-cursor))
+        (setq sr-tree-omit-archives (not sr-tree-omit-archives))
+        (sr-tree-refresh-dir sr-tree-root)
+        (sr-tree-search-cursor cursor)
+        (recenter (truncate (/ (window-body-height) 10.0))))))
+
+(defun sr-tree-handle-mouse-event (e handler)
+  "Handle mouse event E by updating point and calling HANDLER.
+Return t if the event was successfully handled."
+  (when (and e (eq major-mode 'sr-tree-mode))
+    (mouse-set-point e)
+    (when (sr-tree-get-button)
+      (sr-tree-update-cursor)
+      (funcall handler)
+      t)))
+
+(defun sr-tree-mouse-toggle-branch (e)
+  "Open/close (graphically) the folder clicked with the mouse.
+Also handle the case when the click occurs on the path line."
+  (interactive "e")
+  (or (sr-tree-handle-mouse-event e 'sr-tree-toggle-branch)
+      (sr-mouse-advertised-find-file e)))
+
+(defun sr-tree-mouse-focus-branch (e)
+  "Version of `sr-tree-focus-branch' (which see) for the mouse."
+  (interactive "e")
+  (sr-tree-handle-mouse-event e 'sr-tree-focus-branch))
+
+(defun sr-tree-mouse-blur-branch (e)
+  "Version of `sr-tree-blur-branch' (which see) for the mouse."
+  (interactive "e")
+  (or (sr-tree-handle-mouse-event e 'sr-tree-blur-branch)
+      (sr-tree-blur-branch)))
+
+(defun sr-tree-mouse-explode-branch (e)
+  "Version of `sr-tree-explode-branch' (which see) for the mouse."
+  (interactive "e")
+  (sr-tree-handle-mouse-event e 'sr-tree-explode-branch))
+
+;;; ============================================================================
+;;; File system navigation functions:
+
+(defun sr-tree-prev-subdir (&optional keep-state)
+  "Move to the parent of currently selected directory in Tree View mode.
+Resets the list of open directories unless KEEP-STATE is not
+nil."
+  (interactive)
+  (let* ((branch (sr-tree-get-branch))
+         (parent (widget-get branch :parent)))
+    (cond
+     ((tree-widget-p parent)
+      (goto-char (widget-get parent :from))
+      (sr-tree-update-cursor))
+
+     ((sr-equal-dirs default-directory "/")
+      (ignore))
+
+     ((eq branch sr-tree-root)
+      (sr-tree-register-path sr-tree-root)
+      (sr-tree-goto-dir ".." keep-state)
+      (sr-tree-beginning-of-buffer)))))
+
+(defun sr-tree-jump-up ()
+  (interactive)
+  (sr-tree-prev-subdir t)
+  (revert-buffer))
+
+(defun sr-tree-advertised-find-file ()
+  "Visit the currently selected file or directory in Sunrise Tree View mode."
+  (interactive)
+  (let ((target (cdr sr-tree-cursor))
+        (sr-goto-dir-function nil)
+        (in-search (memq 'sr-tree-post-isearch isearch-mode-end-hook)))
+    (sr-tree-check-virtual-size target)
+    (if in-search (sr-tree-isearch-done))
+    (sr-save-aspect (sr-alternate-buffer (sr-goto-dir target)))
+    (if in-search (sr-sticky-isearch))))
+
+(defun sr-tree-mouse-advertised-find-file (e)
+  "Visit a file or directory selected using the mouse in the current pane."
+  (interactive "e")
+  (sr-tree-handle-mouse-event e 'sr-tree-advertised-find-file))
+
+(defun sr-tree-advertised-find-file-other ()
+  "Visit the currently selected file or directory in the passive pane."
+  (interactive)
+  (let ((target (cdr sr-tree-cursor)) (side (sr-other))
+        (sr-inhibit-highlight t))
+    (sr-tree-check-virtual-size target)
+    (save-selected-window
+      (select-window (sr-other 'window))
+      (sr-goto-dir target)
+      (sr-display-attributes (point-min) (point-max) sr-show-file-attributes)
+      (sr-keep-buffer side)
+      (if (fboundp 'sr-modeline-refresh) (sr-modeline-refresh))
+      (if (fboundp 'sr-tabs-refresh) (sr-tabs-refresh)))))
+
+(defun sr-tree-sync ()
+  "Toggle the synchronized navigation feature in Sunrise Tree View panes."
+  (interactive)
+  (sr-sync)
+  (sr-tree-update-cursor))
+
+;;; ============================================================================
+;;; File system manipulation functions:
+
+(defun sr-tree-get-filename (&optional _localp _no-error)
+  "Replacement for `dired-get-filename' in Sunrise Tree functions."
+  (cdr sr-tree-cursor))
+
+(defun sr-tree-show-file-type (_file &optional _deref-symlinks)
+  "Replacement for `dired-show-file-type' in Sunrise Tree functions."
+  (message "%s: directory" (directory-file-name (car sr-tree-cursor))))
+
+(defmacro sr-tree-adapt-dired-command (form)
+  "Evaluate FORM with a few Dired functions locally redefined.
+Necessary so the basic Dired file manipulation commands can work
+in Sunrise Tree View mode."
+  `(let ((ad-redefinition-action 'accept))
+     (letf (((symbol-function 'dired-get-filename)
+             (symbol-function 'sr-tree-get-filename))
+            ((symbol-function 'dired-show-file-type)
+             (symbol-function 'sr-tree-show-file-type)))
+       ,form)))
+
+(defun sr-tree-do-copy ()
+  "Recursively copy all selected files and directories between panes.
+Copies files from the active to the passive pane."
+  (interactive)
+  (sr-tree-adapt-dired-command (sr-do-copy)))
+
+(defun sr-tree-do-clone ()
+  "Recursively clone all selected files and directories between panes.
+Clones files from active to the passive pane."
+  (interactive)
+  (sr-tree-adapt-dired-command (sr-do-clone)))
+
+(defun sr-tree-do-symlink ()
+  "Create symbolic links in the passive pane to selected files in the active pane."
+  (interactive)
+  (sr-tree-adapt-dired-command (sr-do-symlink)))
+
+(defun sr-tree-do-relsymlink ()
+  "Make relative symbolic links in the passive pane to selected files in the active pane."
+  (interactive)
+  (sr-tree-adapt-dired-command (sr-do-relsymlink)))
+
+(defun sr-tree-do-rename ()
+  "Recursively move all selected files and directories between panes.
+Moves files from the active pane to the passive pane."
+  (interactive)
+  (sr-tree-adapt-dired-command (sr-do-rename))
+  (unless (file-exists-p (cdr sr-tree-cursor))
+    (sr-tree-prev-subdir)
+    (sr-tree-refresh-branch)))
+
+(defun sr-tree-do-delete ()
+  "Remove the directory selected in the current Sunrise Tree View pane."
+  (interactive)
+  (sr-tree-adapt-dired-command (sr-do-delete))
+  (unless (file-exists-p (cdr sr-tree-cursor))
+    (sr-tree-prev-subdir)
+    (sr-tree-refresh-branch)))
+
+(defun sr-tree-show-files-info ()
+  "Version of `sr-show-files-info' (which see) for Sunrise Tree View panes."
+  (interactive)
+  (sr-tree-adapt-dired-command (sr-show-files-info)))
+
+(defun sr-tree-create-directory (directory)
+  "Create a new directory in Sunrise Tree View mode."
+  (interactive
+   (list (read-file-name "Create directory: "
+                         (file-name-as-directory (cdr sr-tree-cursor)))))
+  (let* ((expanded (directory-file-name (expand-file-name directory)))
+         (parent (file-name-directory expanded)))
+    (make-directory expanded t)
+    (when (sr-equal-dirs parent (cdr sr-tree-cursor))
+      (sr-tree-toggle-branch 'open)
+      (sr-tree-refresh-branch)
+      (search-forward
+       (file-name-as-directory (file-name-nondirectory expanded)) nil t)
+      (sr-tree-update-cursor))))
+
+;;;###autoload
+(define-derived-mode sr-tree-mode nil "Sunrise Tree View"
+  "Tree view for the Sunrise Commander file manager."
+  :group 'sunrise
+  (set-keymap-parent sr-tree-mode-map sr-mode-map)
+  (mapc 'make-local-variable '(sr-tree-open-paths
+                               sr-tree-cursor
+                               hl-line-sticky-flag
+                               isearch-mode-end-hook
+                               desktop-save-buffer
+                               revert-buffer-function
+                               sr-goto-dir-function
+                               sr-buttons-command-adapter))
+  (add-hook 'isearch-mode-end-hook 'sr-tree-update-cursor)
+  (setq desktop-save-buffer        'sr-tree-desktop-save-buffer
+        hl-line-sticky-flag        nil
+        revert-buffer-function     'sr-tree-revert-buffer
+        sr-goto-dir-function       'sr-tree-goto-dir
+        sr-buttons-command-adapter 'sr-tree-buttons-command-adapter)
+  (setq dired-omit-mode t)
+  (set (make-local-variable 'buffer-quit-function) 'sr-quit)
+  (hl-line-mode 1)
+  (unless sr-tree-root
+    (sr-tree-build default-directory)))
+
+;;; ============================================================================
+;;; Sunrise Core + Tabs + Mode Line integration:
+
+(defun sr-tree-view (&optional desktop-mode)
+  "Switch from Sunrise normal mode to Tree View.
+If DESKTOP-MODE is non-nil, do not kill the current
+buffer (necessary during `desktop-read')."
+  (interactive)
+  (sr-save-aspect
+   (if desktop-mode
+       (switch-to-buffer (generate-new-buffer "Sunrise Tree"))
+     (sr-alternate-buffer
+      (switch-to-buffer (generate-new-buffer "Sunrise Tree"))))
+   (sr-tree-mode))
+  (if (fboundp 'sr-modeline-setup)
+      (sr-modeline-setup))
+  (if (fboundp 'sr-tabs-engage)
+      (sr-tabs-engage))
+  (sr-force-passive-highlight))
+
+(defun sr-tree-mouse-view (e)
+  "Switch from Sunrise normal mode to Tree View using the mouse."
+  (interactive "e")
+  (sr-mouse-change-window e)
+  (sr-tree-view))
+
+(defun sr-tree-dismiss ()
+  "Switch from Tree View to normal mode."
+  (interactive)
+  (let ((target (widget-get sr-tree-root :path))
+        (sr-goto-dir-function nil))
+    (sr-save-aspect
+     (sr-alternate-buffer (sr-goto-dir target)))))
+
+(defun sr-tree-mouse-dismiss (e)
+  "Switch from Tree View to normal mode using the mouse."
+  (interactive "e")
+  (sr-mouse-change-window e)
+  (sr-tree-dismiss))
+
+;;; ============================================================================
+;;; Sunrise Buttons integration:
+
+(defvar sr-tree-button-commands
+  '((sr-do-copy               . sr-tree-do-copy)
+    (sr-do-clone              . sr-tree-do-clone)
+    (sr-do-symlink            . sr-tree-do-symlink)
+    (sr-do-relsymlink         . sr-tree-do-relsymlink)
+    (sr-do-rename             . sr-tree-do-rename)
+    (sr-do-delete             . sr-tree-do-delete)
+    (sr-goto-dir              . sr-goto-dir)
+    (sr-advertised-find-file  . sr-tree-advertised-find-file)
+    (sr-quick-view            . sr-tree-advertised-find-file-other)
+    (sr-dired-prev-subdir     . sr-tree-prev-subdir)
+    (sr-change-window         . sr-change-window)
+    (sr-synchronize-panes     . sr-synchronize-panes)
+    (sr-sync                  . sr-tree-sync)
+    (sr-beginning-of-buffer   . sr-tree-beginning-of-buffer)
+    (sr-end-of-buffer         . sr-tree-end-of-buffer)
+    (sr-term                  . sr-term)
+    (sr-describe-mode         . sr-describe-mode)
+    (sr-transpose-panes       . sr-transpose-panes)
+    (revert-buffer            . revert-buffer)
+    (sr-split-toggle          . sr-split-toggle)
+    (sr-toggle-truncate-lines . sr-toggle-truncate-lines)
+    (dired-create-directory   . sr-tree-create-directory)
+    (sr-history-prev          . sr-history-prev)
+    (sr-history-next          . sr-history-next)
+    ) "Sunrise Buttons-to-Tree commands translation table.")
+
+(defun sr-tree-buttons-command-adapter (command)
+  "Execute the given buttons command in the current Sunrise Tree View pane.
+If the command doesn't make sense in the current context, first
+switch to normal mode, then execute."
+  (let ((translation (cdr (assq command sr-tree-button-commands))))
+    (if translation
+        (call-interactively translation)
+      (sr-tree-dismiss)
+      (call-interactively command))))
+
+;;; ============================================================================
+;;; Tree Widget adapter functions:
+
+(defun sr-tree-widget-forward (arg)
+  "`widget-forward' replacement for use in `tree-widget-button-keymap'."
+  (interactive "p")
+  (if (eq major-mode 'sr-tree-mode)
+      (sr-change-window)
+    (widget-forward arg)))
+
+(defun sr-tree-widget-button-press (pos &optional event)
+  "`widget-button-press' replacement for use in `tree-widget-button-keymap'."
+  (interactive "@d")
+  (if (eq major-mode 'sr-tree-mode)
+      (sr-tree-open-branch)
+    (widget-button-press pos event)))
+
+(defun sr-tree-widget-button-click (event)
+  "`widget-button-click' replacement for use in `tree-widget-button-keymap'."
+  (interactive "e")
+  (unless (eq major-mode 'sr-tree-mode)
+    (tree-widget-button-click event)))
+
+;;; ============================================================================
+;;; Sunrise Tree View keybindings:
+
+(define-key sr-mode-map "\C-t "             'sr-tree-view)
+(define-key sr-mode-map "\C-t\C-m"          'sr-tree-view)
+(define-key sr-mode-map [(shift meta down)] 'sr-tree-view)
+(define-key sr-mode-map [?\e down]          'sr-tree-view)
+
+(define-key sr-tree-mode-map "\C-m"     'sr-tree-open-branch)
+(define-key sr-tree-mode-map " "        'sr-tree-toggle-branch)
+(define-key sr-tree-mode-map "\C-o"     'sr-tree-omit-mode)
+(define-key sr-tree-mode-map "n"        'sr-tree-next-line)
+(define-key sr-tree-mode-map "p"        'sr-tree-previous-line)
+(define-key sr-tree-mode-map "\t"       'sr-change-window)
+(define-key sr-tree-mode-map "g"        'sr-tree-refresh-branch)
+(define-key sr-tree-mode-map "J"        'sr-tree-jump-up)
+(define-key sr-tree-mode-map "j"        'sr-tree-build-new)
+(define-key sr-tree-mode-map "f"        'sr-tree-advertised-find-file)
+(define-key sr-tree-mode-map "v"        'sr-tree-advertised-find-file-other)
+(define-key sr-tree-mode-map "o"        'sr-tree-advertised-find-file-other)
+(define-key sr-tree-mode-map "\M-a"     'sr-tree-beginning-of-buffer)
+(define-key sr-tree-mode-map "\M-e"     'sr-tree-end-of-buffer)
+(define-key sr-tree-mode-map "\C-s"     'sr-tree-isearch-forward)
+(define-key sr-tree-mode-map "\C-cs"    'sr-tree-sticky-isearch-forward)
+(define-key sr-tree-mode-map "\C-r"     'sr-tree-isearch-backward)
+(define-key sr-tree-mode-map "\C-cr"    'sr-tree-sticky-isearch-backward)
+(define-key sr-tree-mode-map "\C-c\C-c" 'sr-tree-dismiss)
+(define-key sr-tree-mode-map "\C-cf"    'sr-tree-focus-branch)
+(define-key sr-tree-mode-map "\C-cb"    'sr-tree-blur-branch)
+(define-key sr-tree-mode-map "\C-c\C-m" 'sr-tree-explode-branch)
+(define-key sr-tree-mode-map "\C-t "    'sr-tree-dismiss)
+(define-key sr-tree-mode-map "\C-t\C-m" 'sr-tree-dismiss)
+
+(define-key sr-tree-mode-map "#" 'sr-tree-avfs-toggle)
+
+(define-key sr-tree-mode-map [up]   'sr-tree-previous-line)
+(define-key sr-tree-mode-map [down] 'sr-tree-next-line)
+
+(define-key sr-tree-mode-map [right]     'sr-tree-open-branch)
+(define-key sr-tree-mode-map [S-right]   'sr-tree-focus-branch)
+(define-key sr-tree-mode-map [?\e right] 'sr-tree-focus-branch)
+(define-key sr-tree-mode-map [C-right]   'sr-tree-explode-branch)
+(define-key sr-tree-mode-map [3 right]   'sr-tree-explode-branch) ;; "\C-c <right>"
+
+(define-key sr-tree-mode-map [left]     'sr-tree-collapse-branch)
+(define-key sr-tree-mode-map [S-left]   'sr-tree-blur-branch)
+(define-key sr-tree-mode-map [?\e left] 'sr-tree-blur-branch)
+(define-key sr-tree-mode-map [C-left]   'sr-tree-collapse-branch)
+(define-key sr-tree-mode-map [3-left]   'sr-tree-collapse-branch) ;; "\C-c <left>"
+(define-key sr-tree-mode-map [delete]   'sr-tree-collapse-branch)
+
+(define-key sr-tree-mode-map [next]              'sr-tree-scroll-up)
+(define-key sr-tree-mode-map [prior]             'sr-tree-scroll-down)
+(define-key sr-tree-mode-map [backspace]         'sr-tree-collapse-branch)
+(define-key sr-tree-mode-map [C-return]          'sr-tree-explode-branch)
+(define-key sr-tree-mode-map [S-return]          'sr-tree-focus-branch)
+(define-key sr-tree-mode-map [M-return]          'sr-tree-blur-branch)
+(define-key sr-tree-mode-map [(shift meta down)] 'sr-tree-dismiss)
+(define-key sr-tree-mode-map [?\e down]          'sr-tree-dismiss)
+
+(define-key sr-tree-mode-map "C" 'sr-tree-do-copy)
+(define-key sr-tree-mode-map "K" 'sr-tree-do-clone)
+(define-key sr-tree-mode-map "R" 'sr-tree-do-rename)
+(define-key sr-tree-mode-map "D" 'sr-tree-do-delete)
+(define-key sr-tree-mode-map "S" 'sr-tree-do-symlink)
+(define-key sr-tree-mode-map "Y" 'sr-tree-do-relsymlink)
+(define-key sr-tree-mode-map "y" 'sr-tree-show-files-info)
+(defi