Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
303 lines (263 sloc) 14.6 KB
;;; 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. <>
;; Štěpán Němec <>
;; Maintainer: José Alfredo Romero L. <>
;; Created: 11 Jun 2008
;; Version: 1
;; RCS Version: $Rev: 444 $
;; Keywords: sunrise commander, shortcut buttons
;; URL:
;; 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 <>.
;;; 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")
("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")
("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")
("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")
("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")
("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.")
(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."
(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))
(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*"))
(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-select-window sr-selected-window)))
(defun sr-buttons-render ()
"Populate current buffer with all widgets described in `sr-buttons-list'."
(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))
(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
(if (or (null spec)
(> (+ (current-column) maxlen) (- (window-width) (/ maxlen 2))))
(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)
(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)))
(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
(let* ((fill-length (- total-length (length tag)))
(before (/ fill-length 2))
(after (- fill-length before)))
(concat (make-string before fill-char)
(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)
(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)
(defun sr-buttons-editable-pane ()
"Call `sr-editable-pane' and display an informative message.
Used inside the Sunrise Buttons buffer."
(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."
(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