emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] master 500f08c: multishell - new package


From: ken manheimer
Subject: [elpa] master 500f08c: multishell - new package
Date: Sat, 02 Jan 2016 22:20:18 +0000

branch: master
commit 500f08cf9edde5448a4c7b22a21ba6ebe9d8f8b2
Author: Ken Manheimer <address@hidden>
Commit: Ken Manheimer <address@hidden>

    multishell - new package
    
    Author: Ken Manheimer <ken dot manheimer at gmail...>
    Version: 1.0.0
    Maintainer: Ken Manheimer <ken dot manheimer at gmail...>
    Created: 1999 -- first public availability
    Keywords: processes
    URL: https://github.com/kenmanheimer/EmacsUtils
    
    Commentary:
    
    Easily use and manage multiple shell buffers, including remote shells.
    Fundamentally, multishell is the function `multishell:pop-to-shell - like
    pop-to-buffer - plus a keybinding. Together, they enable you to:
    
    * Get to the input point from wherever you are in a shell buffer,
    * ... or to a shell buffer if you're not currently in one.
    * Use universal arguments to launch and choose among alternate shell 
buffers,
    * ... and select which is default.
    * Prepend a path to a new shell name to launch a shell in that directory,
    * ... and use a path with Emacs tramp syntax to launch a remote shell.
    
    Customize-group `multishell` to select and activate a keybinding and set
    various behaviors.
    
    See the pop-to-shell docstring for details.
---
 packages/multishell/multishell.el |  410 +++++++++++++++++++++++++++++++++++++
 1 files changed, 410 insertions(+), 0 deletions(-)

diff --git a/packages/multishell/multishell.el 
b/packages/multishell/multishell.el
new file mode 100644
index 0000000..d859f98
--- /dev/null
+++ b/packages/multishell/multishell.el
@@ -0,0 +1,410 @@
+;;; multishell.el --- manage interaction with multiple local and remote shells
+
+;; Copyright (C) 1999-2016 Free Software Foundation, Inc. and Ken Manheimer
+
+;; Author: Ken Manheimer <ken dot manheimer at gmail...>
+;; Version: 1.0.0
+;; Maintainer: Ken Manheimer <ken dot manheimer at gmail...>
+;; Created: 1999 -- first public availability
+;; Keywords: processes
+;; URL: https://github.com/kenmanheimer/EmacsUtils
+;;
+;;; Commentary:
+;;
+;; Easily use and manage multiple shell buffers, including remote shells.
+;; Fundamentally, multishell is the function `multishell:pop-to-shell - like
+;; pop-to-buffer - plus a keybinding. Together, they enable you to:
+;;
+;; * Get to the input point from wherever you are in a shell buffer,
+;; * ... or to a shell buffer if you're not currently in one.
+;; * Use universal arguments to launch and choose among alternate shell 
buffers,
+;; * ... and select which is default.
+;; * Prepend a path to a new shell name to launch a shell in that directory,
+;; * ... and use a path with Emacs tramp syntax to launch a remote shell.
+;;
+;; Customize-group `multishell` to select and activate a keybinding and set
+;; various behaviors.
+;;
+;; See the pop-to-shell docstring for details.
+;;
+;;; Change Log:
+;;
+;; 2016-01-02 Ken Manheimer - initial release
+;;
+;;; TODO:
+;;
+;; * Preserveable (savehist) history that associates names with paths
+;;   - Using an association list between names and paths
+;;   - Searched for search backwards/forwards on isearch-like M-r/M-s bindings
+;;     - *Not* searched for regular completion
+;;   - Editible
+;;     - Using isearch keybinding M-e
+;;     - Edits path
+;;     - New association overrides previous
+;;     - Deleting path removes association and history entry
+;; * Customize activation of savehist
+;;   - Customize entry has warning about activating savehist
+;;   - Adds the name/path association list to savehist-additional-variables
+;;   - Activates savehist, if inactive
+
+;;; Code:
+
+(defvar non-interactive-process-buffers '("*compilation*" "*grep*"))
+
+(require 'comint)
+(require 'shell)
+
+(defgroup multishell nil
+  "Allout extension that highlights outline structure graphically.
+
+Customize `allout-widgets-auto-activation' to activate allout-widgets
+with allout-mode."
+  :group 'shell)
+
+(defcustom multishell:non-interactive-process-buffers
+  '("*compilation*" "*grep*")
+  "Names of buffers that have processes but are not for interaction.
+Add names of buffers that you don't want pop-to-shell to stick around in."
+  :type '(repeat string)
+  :group 'multishell)
+(defcustom multishell:command-key "\M- "
+  "The key to use if `multishell:activate-command-key' is true.
+
+You can instead bind `pop-to-shell` to your preferred key using emacs
+lisp, eg: (global-set-key \"\\M- \" 'pop-to-shell)."
+  :type 'key-sequence
+  :group 'multishell)
+
+(defvar multishell:responsible-for-command-key nil
+  "Multishell internal.")
+(defun multishell:activate-command-key-setter (symbol setting)
+  "Implement `multishell:activate-command-key' choice."
+  (set-default 'multishell:activate-command-key setting)
+  (when (or setting multishell:responsible-for-command-key)
+    (multishell:implement-command-key-choice (not setting))))
+(defun multishell:implement-command-key-choice (&optional unbind)
+  "If settings dicate, implement binding of multishell command key.
+
+If optional UNBIND is true, globally unbind the key.
+
+* `multishell:activate-command-key' - Set this to get the binding or not.
+* `multishell:command-key' - The key to use for the binding, if appropriate."
+  (cond (unbind
+         (when (and (boundp 'multishell:command-key) multishell:command-key)
+           (global-unset-key multishell:command-key)))
+        ((not (and (boundp 'multishell:activate-command-key)
+                   (boundp 'multishell:command-key)))
+         nil)
+        ((and multishell:activate-command-key multishell:command-key)
+         (setq multishell:responsible-for-command-key t)
+         (global-set-key multishell:command-key 'pop-to-shell))))
+
+(defcustom multishell:activate-command-key nil
+  "Set this to impose the `multishell:command-key' binding.
+
+You can instead bind `pop-to-shell` to your preferred key using emacs
+lisp, eg: (global-set-key \"\\M- \" 'pop-to-shell)."
+  :type 'boolean
+  :set 'multishell:activate-command-key-setter
+  :group 'multishell)
+
+;; Assert the customizations whenever the package is loaded:
+(with-eval-after-load "multishell"
+  (multishell:implement-command-key-choice))
+
+(defcustom multishell:pop-to-frame nil
+  "*If non-nil, jump to a frame already showing the shell, if another is.
+
+Otherwise, open a new window in the current frame.
+
+\(Adjust `pop-up-windows' to change other-buffer vs current-buffer behavior.)"
+  :type 'boolean
+  :group 'multishell)
+
+;; (defcustom multishell:persist-shell-names nil
+;;   "Remember shell name/path associations across sessions. Note well:
+;; This will activate minibuffer history persistence, in general, if it's not
+;; already active."
+;;   :type 'boolean
+;;  :group 'shell)
+
+(defvar multishell:name-path-assoc nil
+  "Assoc list from name to path")
+
+(defvar multishell:primary-name "*shell*"
+  "Shell name to use for un-modified pop-to-shell buffer target.")
+(defvar multishell:buffer-name-history nil
+  "Distinct pop-to-shell completion history container.")
+
+(defun pop-to-shell (&optional arg)
+  "Easily navigate to and within multiple shell buffers, local and remote.
+
+Use universal arguments to launch and choose between alternate
+shell buffers and to select which is default.  Prepend a path to
+a new shell name to launch a shell in that directory, and use
+Emacs tramp syntax to launch a remote shell.
+
+Customize-group `multishell' to set up a key binding and tweak behaviors.
+
+==== Basic operation:
+
+ - If the current buffer is associated with a subprocess (that is
+   not among those named on `non-interactive-process-buffers'),
+   then focus is moved to the process input point.
+
+   \(You can use a universal argument go to a different shell
+   buffer when already in a buffer that has a process - see
+   below.)
+
+ - If not in a shell buffer (or with universal argument), go to a
+   window that is already showing the (a) shell buffer, if any.
+
+   In this case, the cursor is left in its prior position in the
+   shell buffer. Repeating the command will then go to the
+   process input point, per the first item in this list.
+
+   We respect `pop-up-windows', so you can adjust it to set the
+   other-buffer/same-buffer behavior.
+
+ - Otherwise, start a new shell buffer, using the current
+   directory as the working directory.
+
+If a buffer with the resulting name exists and its shell process
+was disconnected or otherwise stopped, it's resumed.
+
+===== Universal arg to start and select between named shell buffers:
+
+You can name alternate shell buffers to create or return to using
+single or doubled universal arguments:
+
+ - 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'.
+
+   Completion is available.
+
+   This combination makes it easy to start and switch between
+   multiple shell buffers.
+
+ - A double universal argument will prompt for the name *and* set
+   the default to that name, so the target shell becomes the
+   primary.
+
+===== Select starting directory and remote host:
+
+The shell buffer name you give to the prompt for a universal arg
+can include a preceding 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
+the buffer name. Otherwise, the host, domain, or path is used.
+
+For example:
+
+* Use '/ssh:example.net:/' for a shell buffer on example.net named
+  \"example.net\".
+* '/ssh:example.net|sudo:address@hidden:/\#ex' for a root shell on 
+  example.net named \"#ex\"."
+
+;; I'm leaving the following out of the docstring for now because just
+;; saving the buffer names, and not the paths, yields sometimes unwanted
+;; behavior.
+
+;; ===== Persisting your alternate shell buffer names and paths:
+
+;; You can use emacs builtin SaveHist to preserve your alternate
+;; shell buffer names and paths across emacs sessions. To do so,
+;; customize the `savehist' group, and:
+
+;; 1. Add `multishell:pop-to-shell-buffer-name-history' to Savehist Additional
+;;    Variables.
+;; 2. Activate Savehist Mode, if not already activated.
+;; 3. Save.
+
+  (interactive "P")
+
+  (let* ((from-buffer (current-buffer))
+         (from-buffer-is-shell (eq major-mode 'shell-mode))
+         (doublearg (equal arg '(16)))
+         (temp (if arg
+                   (multishell:read-bare-shell-buffer-name
+                    (format "Shell buffer name [%s]%s "
+                            (substring-no-properties
+                             multishell:primary-name
+                             1 (- (length multishell:primary-name) 1))
+                            (if doublearg " <==" ":"))
+                    multishell:primary-name)
+                 multishell:primary-name))
+         use-default-dir
+         (target-shell-buffer-name
+          ;; Derive target name, and default-dir if any, from temp.
+          (cond ((string= temp "") multishell:primary-name)
+                ((string-match "^\\*\\(/.*/\\)\\(.*\\)\\*" temp)
+                 (setq use-default-dir (match-string 1 temp))
+                 (multishell:bracket-asterisks 
+                  (if (string= (match-string 2 temp) "")
+                      (let ((v (tramp-dissect-file-name
+                                use-default-dir)))
+                        (or (tramp-file-name-host v)
+                            (tramp-file-name-domain v)
+                            (tramp-file-name-localname v)
+                            use-default-dir))
+                    (match-string 2 temp))))
+                (t (multishell:bracket-asterisks temp))))
+         (curr-buff-proc (get-buffer-process from-buffer))
+         (target-buffer (if (and (or curr-buff-proc from-buffer-is-shell)
+                                 (not (member (buffer-name from-buffer)
+                                              
non-interactive-process-buffers)))
+                            from-buffer
+                          (get-buffer target-shell-buffer-name)))
+         inwin
+         already-there)
+
+    (when doublearg
+      (setq multishell:primary-name target-shell-buffer-name))
+
+    ;; Situate:
+
+    (cond 
+
+     ((and (or curr-buff-proc from-buffer-is-shell)
+           (not arg)
+           (eq from-buffer target-buffer)
+           (not (eq target-shell-buffer-name (buffer-name from-buffer))))
+      ;; In a shell buffer, but not named - stay in buffer, but go to end.
+      (setq already-there t))
+
+     ((string= (buffer-name) target-shell-buffer-name)
+      ;; Already in the specified shell buffer:
+      (setq already-there t))
+
+     ((or (not target-buffer)
+          (not (setq inwin
+                     (multishell:get-visible-window-for-buffer 
target-buffer))))
+      ;; No preexisting shell buffer, or not in a visible window:
+      (pop-to-buffer target-shell-buffer-name pop-up-windows))
+
+       ;; Buffer exists and already has a window - jump to it:
+     (t (if (and multishell:pop-to-frame
+                 inwin
+                 (not (equal (window-frame (selected-window))
+                             (window-frame inwin))))
+            (select-frame-set-input-focus (window-frame inwin)))
+        (if (not (string= (buffer-name (current-buffer))
+                          target-shell-buffer-name))
+            (pop-to-buffer target-shell-buffer-name t))))
+
+    ;; We're in the buffer.
+
+    ;; If we have a use-default-dir, impose it:
+    (when use-default-dir
+        (cd use-default-dir))
+
+    ;; Activate:
+
+    (if (not (comint-check-proc (current-buffer)))
+        (multishell:start-shell-in-buffer (buffer-name (current-buffer))))
+
+    ;; If the destination buffer has a stopped process, resume it:
+    (let ((process (get-buffer-process (current-buffer))))
+      (if (and process (equal 'stop (process-status process)))
+          (continue-process process)))
+    (when (or already-there
+             (equal (current-buffer) from-buffer))
+      (goto-char (point-max))
+      (and (get-buffer-process from-buffer)
+           (goto-char (process-mark (get-buffer-process from-buffer)))))))
+
+(defun multishell:get-visible-window-for-buffer (buffer)
+  "Return visible window containing buffer."
+  (catch 'got-a-vis
+    (walk-windows
+     (function (lambda (win)
+                 (if (and (eq (window-buffer win) buffer)
+                          (equal (frame-parameter
+                                  (selected-frame) 'display)
+                                 (frame-parameter
+                                  (window-frame win) 'display)))
+                     (throw 'got-a-vis win))))
+     nil 'visible)
+    nil))
+
+(defun multishell:read-bare-shell-buffer-name (prompt default)
+  "PROMPT for shell buffer name, sans asterisks.
+
+Return the supplied name bracketed with the asterisks, or specified DEFAULT
+on empty input."
+  (let* ((candidates (append
+                      (remq nil
+                            (mapcar (lambda (buffer)
+                                      (let ((name (buffer-name buffer)))
+                                        (if (with-current-buffer buffer
+                                              (eq major-mode 'shell-mode))
+                                            ;; Shell mode buffers.
+                                            (if (> (length name) 2)
+                                                ;; Strip asterisks.
+                                                (substring name 1
+                                                           (1- (length name)))
+                                              name))))
+                                    (buffer-list)))))
+         (got (completing-read prompt
+                               candidates ; COLLECTION
+                               nil        ; PREDICATE
+                               'confirm   ; REQUIRE-MATCH
+                               nil        ; INITIAL-INPUT
+                               'multishell:buffer-name-history ; HIST
+                               )))
+    (if (not (string= got "")) (multishell:bracket-asterisks got) default)))
+
+(defun multishell:bracket-asterisks (name)
+  "Return a copy of name, ensuring it has an asterisk at the beginning and 
end."
+  (if (not (string= (substring name 0 1) "*"))
+      (setq name (concat "*" name)))
+  (if (not (string= (substring name -1) "*"))
+      (setq name (concat name "*")))
+  name)
+(defun multishell:unbracket-asterisks (name)
+  "Return a copy of name, removing asterisks, if any, at beginning and end."
+  (if (string= (substring name 0 1) "*")
+      (setq name (substring name 1)))
+  (if (string= (substring name -1) "*")
+      (setq name (substring name 0 -1)))
+  name)
+(defun multishell:start-shell-in-buffer (buffer-name)
+  "Ensure a shell is started, using whatever name we're passed."
+  ;; We work around shell-mode's bracketing of the buffer name, and do
+  ;; some tramp-mode hygiene for remote connections.
+
+  (require 'comint)
+  (require 'shell)
+
+  (let* ((buffer buffer-name)
+         (prog (or explicit-shell-file-name
+                   (getenv "ESHELL")
+                   (getenv "SHELL")
+                   "/bin/sh"))
+         (name (file-name-nondirectory prog))
+         (startfile (concat "~/.emacs_" name))
+         (xargs-name (intern-soft (concat "explicit-" name "-args"))))
+    (set-buffer buffer-name)
+    (when (and (file-remote-p default-directory)
+               (eq major-mode 'shell-mode)
+               (not (comint-check-proc (current-buffer))))
+      ;; We're returning to an already established but disconnected remote
+      ;; shell, tidy it:
+      (tramp-cleanup-connection
+       (tramp-dissect-file-name default-directory 'noexpand)
+       'keep-debug 'keep-password))
+    (setq buffer (set-buffer (apply 'make-comint
+                                    (multishell:unbracket-asterisks 
buffer-name)
+                                    prog
+                                    (if (file-exists-p startfile)
+                                        startfile)
+                                    (if (and xargs-name
+                                             (boundp xargs-name))
+                                        (symbol-value xargs-name)
+                                      '("-i")))))
+    (shell-mode)))
+
+(provide 'multishell)
+
+;;; multishell.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]