-;;; multishell.el --- facilitate multiple local and remote shell buffers
+;;; multishell.el --- Easily use multiple shell buffers, local and remote.
;; Copyright (C) 1999-2016 Free Software Foundation, Inc.
;; Author: Ken Manheimer <ken.manheimer@gmail.com>
-;; Version: 1.1.3
+;; Version: 1.1.5
;; Created: 1999 -- first public availability
;; Keywords: processes
;; URL: https://github.com/kenmanheimer/EmacsMultishell
;;
;; Change Log:
;;
+;; * 2016-02-11 1.1.5 Ken Manheimer:
+;; - Rectify multishell list sorting to preserve recentness
+;; - Increment the actual multishell-version setting, neglected for 1.1.4.
+;; * 2016-02-11 1.1.4 Ken Manheimer:
+;; - hookup multishell-list as completion help buffer.
+;; Mouse and keyboard selections from help listing properly exits
+;; minibuffer.
;; * 2016-02-09 1.1.3 Ken Manheimer:
;; multishell-list:
;; - add some handy operations, like cloning new entry from existing
;;
;; TODO and Known Issues:
;;
-;; * Add mouse actions - buttons - to multishell-list entries
-;; - see buf-menu.el, eg Buffer-menu-mouse-select
-;; * Resolve multishell-list sort glitches:
-;; - Fix config so multishell-list-revert-buffer-kludge is not needed
-;; - Make multishell-list-edit-entry in-place, so changed entries recency
-;; doesn't change.
-;; - Fill in kill-buffer prompting gaps, eg if default live-process
-;; prompts are inhibited.
;; * Add custom shell launch prep actions
;; - for, eg, port knocking, interface activations
;; - shell commands to execute when shell name or path matches a regexp
;; - list of (regexp, which - name, path, or both, command)
-;; * Adapt multishell-list facilities for all-completions
-;; - See info on minibuffer-completion-help, display-completion-list
;; * Investigate whether we can recognize and provide for failed hops.
;; - Tramp doesn't provide useful reactions for any hop but the first
;; - Might be stuff we can do to detect and convey failures?
(require 'savehist)
(require 'multishell-list)
-(defvar multishell-version "1.1.3")
+(defvar multishell-version "1.1.5")
(defun multishell-version (&optional here)
"Return string describing the loaded multishell version."
(interactive "P")
(defcustom multishell-command-key "\M- "
"The key to use if `multishell-activate-command-key' is true.
-You can instead manually bind `multishell-pop-to-shell` using emacs
-lisp, eg: (global-set-key \"\\M- \" 'multishell-pop-to-shell)."
+You can instead manually bind `multishell-pop-to-shell' using emacs
+lisp, eg: (global-set-key \"\\M- \" \\='multishell-pop-to-shell)."
:type 'key-sequence)
(defvar multishell--responsible-for-command-key nil
(defcustom multishell-activate-command-key nil
"Set this to impose the `multishell-command-key' binding.
-You can instead manually bind `multishell-pop-to-shell` using emacs
-lisp, eg: (global-set-key \"\\M- \" 'multishell-pop-to-shell)."
+You can instead manually bind `multishell-pop-to-shell' using emacs
+lisp, eg: (global-set-key \"\\M- \" \\='multishell-pop-to-shell)."
:type 'boolean
:set 'multishell-activate-command-key-setter)
`savehist-additional-variables' to include the
`multishell-primary-name'.")
+(defvar multishell-completing-read nil
+ "Internal use, conveying whether or not we're in the midst of a multishell
+completing-read.")
+
;; Multiple entries happen because completion also adds name to history.
(defun multishell-register-name-to-path (name path)
"Add or replace entry associating NAME with PATH in `multishell-history'.
- With a single universal argument, prompt for the buffer name
to use (without the asterisks that shell mode will put around
- the name), defaulting to 'shell'.
+ the name), defaulting to `shell'.
Completion is available.
The shell buffer name you give to the prompt for a universal arg
can include an appended path. That will be used for the startup
directory. You can use tramp remote syntax to specify a remote
-shell. If there is an element after a final '/', that's used for
+shell. If there is an element after a final `/', that's used for
the buffer name. Otherwise, the host, domain, or path is used.
For example:
-* '#root/sudo:root@localhost:/etc' for a buffer named \"*#root*\" with a
+* `#root/sudo:root@localhost:/etc' for a buffer named \"*#root*\" with a
root shell starting in /etc.
-* '/ssh:example.net:' for a shell buffer in your homedir on example.net.
+* `/ssh:example.net:' for a shell buffer in your homedir on example.net.
The buffer will be named \"*example.net*\".
-* '#ex/ssh:example.net|sudo:root@example.net:/var/log' for a root shell
+* `#ex/ssh:example.net|sudo:root@example.net:/var/log' for a root shell
starting in /var/log on example.net named \"*#ex*\".
-* 'interior/ssh:gateway.corp.com|ssh:interior.corp.com:' to go
+* `interior/ssh:gateway.corp.com|ssh:interior.corp.com:' to go
via gateway.corp.com to your homedir on interior.corp.com. The
buffer will be named \"*interior*\". You could append a sudo
hop to the path, combining the previous example, and so on.
(let ((token '(token)))
(if (window-minibuffer-p)
- (throw 'multishell-do-list token)
- (if (equal token
- (catch 'multishell-do-list
- (multishell-pop-to-shell-worker arg name here)))
- (multishell-list)))))
+ (throw 'multishell-minibuffer-exit token)
+ (let ((got (catch 'multishell-minibuffer-exit
+ (multishell-pop-to-shell-worker arg name here))))
+ ;; Handle catch or plain fall-through - see cond comments for protocol.
+ (cond
+ ;; Caught token from recursive invocation in minibuffer:
+ ((equal token got) (multishell-list))
+ ;; Caught specifaction of multishell args, eg from multishell-list:
+ ((listp got) (multishell-pop-to-shell-worker (nth 2 got)
+ (nth 0 got)
+ (nth 1 got)))
+ ;; Regular fallthrough - just relay the result:
+ (t got))))))
(defun multishell-pop-to-shell-worker (&optional arg name here)
"Do real work of `multishell-pop-to-shell', which see."
;; Situate:
- (cond
+ (cond
((and (or curr-buff-proc from-buffer-is-shell)
(not arg)
Return what's provided, if anything, else nil."
(let* ((was-multishell-history multishell-history)
(candidates (multishell-all-entries 'active-duplicated))
- (got (completing-read prompt
+ (multishell-completing-read t)
+ (got
+ ;; Use `cl-letf' to dynamically bind multishell-list to
+ ;; display-completion-list, so multishell-list is used when doing
+ ;; minibuffer-completion-help.
+ (cl-letf (((symbol-function 'display-completion-list)
+ #'multishell-list))
+ (completing-read prompt
;; COLLECTION:
(reverse candidates)
;; PREDICATE:
;; INITIAL-INPUT
initial
;; HIST:
- 'multishell-history)))
+ 'multishell-history))))
(when no-record
(setq multishell-history was-multishell-history))
(if (not (string= got ""))
(defun multishell-resolve-target-name-and-path (shell-spec)
"Given name/tramp-style address shell spec, resolve buffer name and directory.
-The name is the part of the string up to the first '/' slash, if
+The name is the part of the string up to the first `/' slash, if
any. Missing pieces are filled in from remote path elements, if
any, and multishell history. Given a tramp-style remote address
and no name part, either the user@host is used for the buffer
(when (and (derived-mode-p 'shell-mode) (file-remote-p path))
;; Returning to disconnected remote shell - do some tidying.
- ;; (Prevents the "Args out of range" failure when reconnecting.)
+ ;; Without this cleanup, occasionally restarting a disconnected
+ ;; remote session, particularly one that includes sudo, results in
+ ;; an untraceable "Args out of range" error. That never happens if
+ ;; we precedeed connection attempts with this cleanup -
+ ;; prophylactic.
(tramp-cleanup-connection
(tramp-dissect-file-name default-directory 'noexpand)
'keep-debug 'keep-password))