[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
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [elpa] master 500f08c: multishell - new package,
ken manheimer <=