+Optional INITIAL is preliminary value to be edited.
+
+Optional NO-RECORD prevents changes to `multishell-history'
+across the activity.
+
+Input and completion can include associated path, if any.
+
+Return what's provided, if anything, else nil."
+ (let* ((was-multishell-history multishell-history)
+ (candidates (multishell-all-entries 'active-duplicated))
+ (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:
+ nil
+ ;; REQUIRE-MATCH:
+ 'confirm
+ ;; INITIAL-INPUT
+ initial
+ ;; HIST:
+ 'multishell-history))))
+ (when no-record
+ (setq multishell-history was-multishell-history))
+ (if (not (string= got ""))
+ got
+ nil)))
+
+(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
+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
+name, if a user is specified, or just the host.
+
+Return them as a list: (name path), with name asterisk-bracketed
+and path nil if none is resolved."
+ (let* ((splat (multishell-split-entry (or shell-spec "")))
+ (path (cadr splat))
+ (name (or (car splat) (multishell-name-from-entry path))))
+ (when (not path)
+ ;; Get path from history, if present.
+ (dolist (entry
+ (multishell-history-entries
+ (multishell-unbracket name)))
+ (when (or (not path) (string= path ""))
+ (setq path (cadr (multishell-split-entry entry))))))
+ (list (multishell-bracket name) path)))
+
+(defun multishell-name-from-entry (entry)
+ "Derive a name for a shell buffer according to ENTRY."
+ (if (not entry)
+ (multishell-unbracket multishell-primary-name)
+ (let* ((splat (multishell-split-entry entry))
+ (name (car splat))
+ (path (cadr splat)))
+ (or name
+ (if (file-remote-p path)
+ (let ((host (file-remote-p path 'host))
+ (user (file-remote-p path 'user)))
+ (cond ((and host user)
+ (format "%s@%s" user host))
+ (host host)
+ (user user)
+ ((system-name))))
+ (multishell-unbracket multishell-primary-name))))))
+
+(declare-function tramp-dissect-file-name "tramp")
+(declare-function tramp-cleanup-connection "tramp")
+
+(defun multishell-start-shell-in-buffer (path)
+ "Start, restart, or continue a shell in BUFFER-NAME on PATH."
+ (let* ((is-active (comint-check-proc (current-buffer))))
+
+ (when (and path (not is-active))
+
+ (when (and (derived-mode-p 'shell-mode) (file-remote-p path))
+ ;; Returning to disconnected remote shell - do some tidying.
+ ;; 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))
+
+ (when (file-remote-p path) (message "Connecting to %s" path))
+ (cd path))
+
+ (shell (current-buffer))))
+
+(defun multishell-homedir-shorthand-p (dirpath)
+ "t if dirpath is an unexpanded remote homedir spec."
+ ;; Workaround to recognize tramp-style homedir shorthand, "...:" and "...:~".
+ (let ((localname (file-remote-p dirpath 'localname)))
+ (and localname
+ (or
+ ;; No directory path and no connection to expand homedir:
+ (string= localname "")
+ ;; Original path doesn't equal expanded homedir:
+ (save-match-data
+ (not (string-match (concat (regexp-quote localname) "/?$")
+ dirpath)))))))
+;; (assert (multishell-homedir-shorthand-p "/ssh:myhost.net:")
+;; (assert (not (multishell-homedir-shorthand-p "/home/klm")))
+;; (assert (not (multishell-homedir-shorthand-p "/ssh:myhost.net:/home/me")))
+
+(defun multishell-track-dirchange (name newpath)
+ "Change multishell history entry to track current directory."
+ (let* ((entries (multishell-history-entries name)))
+ (dolist (entry entries)
+ (let* ((name-path (multishell-split-entry entry))
+ (name (car name-path))
+ (path (or (cadr name-path) "")))
+ (when path
+ (let* ((old-localname (or (file-remote-p path 'localname)
+ path))
+ (newentry
+ (if (multishell-homedir-shorthand-p path)
+ (concat entry newpath)
+ (replace-regexp-in-string (concat (regexp-quote
+ old-localname)
+ "$")
+ ;; REPLACEMENT
+ newpath
+ ;; STRING
+ entry
+ ;; FIXEDCASE
+ t
+ ;; LITERAL
+ t
+ )))
+ (membership (member entry multishell-history)))
+ (when membership
+ (setcar membership newentry))))))))
+
+(defvar multishell-was-default-directory ()
+ "Provide for tracking directory changes.")
+(make-variable-buffer-local 'multishell-was-default-directory)
+(defun multishell-post-command-business ()
+ "Do multishell bookkeeping."
+ ;; Update multishell-history with dir changes.
+ (condition-case err
+ (when (and multishell-history-entry-tracks-current-directory
+ (derived-mode-p 'shell-mode))
+ (let ((curdir (if (file-remote-p default-directory)
+ (file-remote-p default-directory 'localname)
+ default-directory)))
+ (when (not (string= curdir (or multishell-was-default-directory "")))
+ (multishell-track-dirchange (multishell-unbracket (buffer-name))
+ curdir))
+ (setq multishell-was-default-directory curdir)))
+ ;; To avoid disruption as a pervasive hook function, swallow all errors:
+ (error
+ (message "multishell-post-command-business error: %s" err))))
+(add-hook 'post-command-hook #'multishell-post-command-business)
+
+(defun multishell-split-entry (entry)
+ "Given multishell name/path ENTRY, return the separated name and path pair.
+
+Returns nil for empty parts, rather than the empty string."
+ (save-match-data
+ (string-match "^\\([^/]*\\)\\(/?.*\\)?" entry)
+ (let ((name (match-string 1 entry))
+ (path (match-string 2 entry)))
+ (and (string= name "") (setq name nil))
+ (and (string= path "") (setq path nil))
+ (list name path))))
+(defun multishell-bracket (name)