multi-term.el

Download Git Homepage

;;; multi-term.el --- Managing multiple terminal buffers in Emacs.

;; Author: Andy Stewart <lazycat.manatee@gmail.com>
;; Maintainer: Andy Stewart <lazycat.manatee@gmail.com>
;; Copyright (C) 2008 ~ 2016 Andy Stewart, all rights reserved.
;; Copyright (C) 2010, ahei, all rights reserved.
;; Created: <2008-09-19 23:02:42>
;; Version: 1.4
;; Last-Updated: 2017-03-03 13:48:38
;; URL: http://www.emacswiki.org/emacs/download/multi-term.el
;; Keywords: term, terminal, multiple buffer
;; Compatibility: GNU Emacs 23.2.1, GNU Emacs 24.4 (and prereleases)

;; 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, 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 details.

;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.

;; Features that might be required by this library:
;;
;;  `term' `cl-lib' `advice'
;;

;;; Commentary:
;;
;; This package is for creating and managing multiple terminal buffers in Emacs.
;;
;; By default, term.el provides a great terminal emulator in Emacs.
;; But I have some troubles with term-mode:
;;
;; 1. term.el just provides commands `term' or `ansi-term'
;;    for creating a terminal buffer.
;;    And there is no special command to create or switch
;;    between multiple terminal buffers quickly.
;;
;; 2. By default, the keystrokes of term.el conflict with global-mode keystrokes,
;;    which makes it difficult for the user to integrate term.el with Emacs.
;;
;; 3. By default, executing *NIX command “exit” from term-mode,
;;    it will leave an unused buffer.
;;
;; 4. term.el won’t quit running sub-process when you kill terminal buffer forcibly.
;;
;; 5. Haven’t a dedicated window for debug program.
;;
;; And multi-term.el is enhanced with those features.
;;

;;; Installation:
;;
;; Copy multi-term.el to your load-path and add to your ~/.emacs
;;
;;  (require 'multi-term)
;;
;; And setup program that `multi-term' will need:
;;
;; (setq multi-term-program "/bin/bash")
;;
;;      or setup like me "/bin/zsh" ;)
;;
;; Below are the commands you can use:
;;
;;      `multi-term'                    Create a new term buffer.
;;      `multi-term-next'               Switch to next term buffer.
;;      `multi-term-prev'               Switch to previous term buffer.
;;      `multi-term-dedicated-open'     Open dedicated term window.
;;      `multi-term-dedicated-close'    Close dedicated term window.
;;      `multi-term-dedicated-toggle'   Toggle dedicated term window.
;;      `multi-term-dedicated-select'   Select dedicated term window.
;;
;; Tips:
;;
;;      You can type `C-u' before command `multi-term' or `multi-term-dedicated-open'
;;      then will prompt you shell name for creating terminal buffer.
;;

;;; Customize:
;;
;; `multi-term-program' default is nil, so when creating new term buffer,
;; send environment variable of `SHELL' (`ESHELL', `/bin/sh') to `make-term'.
;;
;; And you can set it to your liking, like me: ;-)
;;
;; (setq multi-term-program "/bin/zsh")
;;
;; `multi-term-default-dir' default is `~/', only use when current buffer
;; is not in a real directory.
;;
;; `multi-term-buffer-name' is the name of term buffer.
;;
;; `multi-term-scroll-show-maximum-output' controls how interpreter
;; output causes window to scroll.
;;
;; `multi-term-scroll-to-bottom-on-output' controls whether interpreter
;; output causes window to scroll.
;;
;; `multi-term-switch-after-close' try to switch other `multi-term' buffer
;; after close current one.
;; If you don't like this feature just set it with nil.
;;
;; `term-unbind-key-list' is a key list to unbind some keystroke.
;;
;; `term-bind-key-alist' is a key alist that binds some keystroke.
;; If you don't like default, modify it.
;;
;; `multi-term-dedicated-window-height' the height of a dedicated term window.
;;
;; `multi-term-dedicated-max-window-height' the max height limit that dedicated
;; window is allowed.
;;
;; `multi-term-dedicated-skip-other-window-p' whether skip dedicated term
;; window when use command `other-window' to cycle windows order.
;;
;; All of the above can be customize by:
;;      M-x customize-group RET multi-term RET
;;

;;; Change log:
;;
;; 2017/03/03
;;      * Switch to cl-lib
;;
;; 2016/06/19
;;      * Add Hogren's patch: `term-send-delete-word' and binding to key 'M-d'.
;;
;; 2015/02/20
;;      * Binding C-Backspace to `term-send-backward-kill-word' to follow emacs behaviour.
;;
;; 2014/12/04
;;      * Ernesto Rodriguez Reina <erreina@gmail.com>
;;      Fixed the bug of cursor not return to the position it was before opened the dedicated terminal window when
;;      `multi-term-dedicated-close-back-to-open-buffer-p' and `multi-term-dedicated-select-after-open-p' are t.
;;
;; 2014/08/27
;;      * Kevin Peng <kkpengboy@gmail.com>
;;      Keep multi-term buffer list make multi-term-next/prev can switch temrinal buffer even terminal buffer's name is changed.
;;
;; 2014/07/21
;;      * Andy Stewart
;;      Bind C-m with `term-send-return' instead `term-send-input' to fixed bug that
;;      duplicate input when you C-a and C-m in terminal.
;;
;; 2014/06/21
;;      * Fixed bug that can't found define of `multi-term-dedicated-handle-other-window-advice'.
;;
;; 2014/05/12
;;      * Make Emacs 24.4 compatibility cleaner by avoiding version sniffing.
;;
;; 2014/03/23
;;      * Add `term-send-esc' and binding with 'C-c C-e', send esc is useful for some program, such as vim. ;)
;;      * Add new option `multi-term-dedicated-close-back-to-open-buffer-p' .
;;      * Bind C-y with `term-paste' to avoid paste content can't insert in term mode.
;;
;; 2014/03/17   Andy Stewart
;;      * Swap key binding of `term-send-raw' and `term-send-input', i think it's better send yank data when user hit ctrl+m.
;;
;; 2014/01/16
;;      * Fix breakage introduced in Emacs 24.4.
;;
;; 2013/01/08
;;      * Fix customize group of `multi-term-try-create'.
;;      * Add autoloads.
;;      * Add new commands `term-send-quote' and `term-send-M-x'.
;;
;; 2009/07/04
;;      * Add new option `multi-term-dedicated-select-after-open-p'.
;;
;; 2009/06/29
;;      * Fix regexp bug.
;;
;; 2009/04/21
;;      * Fix a bug that bring at `2009/03/28':
;;        It will kill sub-process in other multi-term buffer
;;        when we kill current multi-term buffer.
;;
;; 2009/03/29
;;      * Add new command `term-send-reverse-search-history'.
;;
;; 2009/03/28
;;      * Add new option `multi-term-switch-after-close'.
;;
;; 2009/02/18
;;      * Fix bug between ECB and `multi-term-dedicated-close'.
;;
;; 2009/02/05
;;      * Prompt user shell name when type `C-u' before command
;;        `multi-term' or `multi-term-dedicated-open'.
;;      * Fix doc.
;;
;; 2009/01/29
;;      * Use `term-quit-subjob' instead `term-interrupt-subjob'.
;;      * Fix doc.
;;
;; 2009/01/13
;;      * Rewrite advice for `pop-to-buffer' to avoid `pop-to-buffer' not effect
;;        when have many dedicated window in current frame.
;;      * Rewrite advice for `delete-other-windows' to avoid use common variable
;;        `delete-protected-window-list' and use `window-dedicated-p' instead.
;;        Remove variable `delete-protected-window-list' and function
;;        `multi-term-dedicated-match-protected-window-p'.
;;
;; 2009/01/06
;;      * Improve document.
;;
;; 2008/12/29
;;      * Remove option `multi-term-current-window-height' and
;;        function `multi-term-current-directory'.
;;      * Add some functions to make get dedicated term buffer,
;;        those functions is beginning with `multi-term-dedicated-'.
;;      * Modified advice `delete-window', make command `delete-window'
;;        and delete dedicated window, but will remember window height
;;        before deleted.
;;      * Don't remember dedicated window height if larger than max value.
;;      * Fix some bug with `delete-other-windows' and window configuration.
;;        And this bug exists with another extension `sr-speedbar'.
;;      * Add new variable `delete-protected-window-list' for protected
;;        special window that won't be deleted.
;;        This variable is common for any extension that use dedicated
;;        window.
;;      * Fix doc.
;;
;; 2008/12/21
;;      * Default bind `C-m' with `term-send-input'.
;;
;; 2008/12/10
;;      * Improve customize interface.
;;      * Setup customize automatically, don't need to user setup it up.
;;      * Add option `multi-term-try-create'.
;;      * Make function `multi-term-switch' accept offset argument.
;;      * Fix doc.
;;
;; 2008/10/22
;;      * Add variable `multi-term-current-window-height'.
;;      * Add variable `multi-term-buffer-name'.
;;      * Add variable `term-unbind-key-list'.
;;      * Add variable `term-rebind-key-alist'.
;;      * Move key setup and some extension from `term-extension.el'.
;;      * Create new function `multi-term-keystroke-setup'.
;;      * Fix doc.
;;
;; 2008/09/19
;;      * First released.
;;

;;; Acknowledgments:
;;
;;      Mark Triggs     <mst@dishevelled.net>
;;              For create multi-shell.el
;;      Aaron S. Hawley <aaron.s.hawley@gmail.com>
;;              For improve document.
;;

;;; Bug
;;
;;

;;; TODO
;;
;;
;;

;;; Require:
(require 'term)
(require 'cl-lib)
(require 'advice)

;;; Code:

;;; Customize

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Customize ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defgroup multi-term nil
  "Multi term manager."
  :group 'term)

(defcustom multi-term-program nil
  "The program of term.
If this is nil, setup to environment variable of `SHELL'."
  :type 'string
  :group 'multi-term)

(defcustom multi-term-program-switches nil
  "The command-line switches to pass to the term program."
  :type 'string
  :group 'multi-term)

(defcustom multi-term-try-create t
  "Try to create a new term buffer when switch.

When use `multi-term-next' or `multi-term-prev', switch term buffer,
and try to create a new term buffer if no term buffers exist."
  :type 'boolean
  :group 'multi-term)

(defcustom multi-term-default-dir "~/"
  "The default directory for terms if current directory doesn't exist."
  :type 'string
  :group 'multi-term)

(defcustom multi-term-buffer-name "terminal"
  "The buffer name of term buffer."
  :type 'string
  :group 'multi-term)

(defcustom multi-term-scroll-show-maximum-output nil
  "*Controls how interpreter output causes window to scroll.
If non-nil, then show the maximum output when the window is scrolled.

See variable `multi-term-scroll-to-bottom-on-output'."
  :type 'boolean
  :group 'multi-term)

(defcustom multi-term-scroll-to-bottom-on-output nil
  "*Controls whether interpreter output causes window to scroll.
If nil, then do not scroll.  If t or `all', scroll all windows showing buffer.
If `this', scroll only the selected window.
If `others', scroll only those that are not the selected window.

The default is nil.

See variable `multi-term-scroll-show-maximum-output'."
  :type 'boolean
  :group 'multi-term)

(defcustom multi-term-switch-after-close 'NEXT
  "Try to switch other `multi-term' buffer after close current one.
If this option is 'NEXT, switch to next `multi-term' buffer;
If this option is 'PREVIOUS, switch to previous `multi-term' buffer.
If this option is nil, don't switch other `multi-term' buffer."
  :type 'symbol
  :group 'multi-term)

(defcustom term-unbind-key-list
  '("C-z" "C-x" "C-c" "C-h" "C-y" "<ESC>")
  "The key list that will need to be unbind."
  :type 'list
  :group 'multi-term)

(defcustom term-bind-key-alist
  '(
    ("C-c C-c" . term-interrupt-subjob)
    ("C-c C-e" . term-send-esc)
    ("C-p" . previous-line)
    ("C-n" . next-line)
    ("C-s" . isearch-forward)
    ("C-r" . isearch-backward)
    ("C-m" . term-send-return)
    ("C-y" . term-paste)
    ("M-f" . term-send-forward-word)
    ("M-b" . term-send-backward-word)
    ("M-o" . term-send-backspace)
    ("M-p" . term-send-up)
    ("M-n" . term-send-down)
    ("M-M" . term-send-forward-kill-word)
    ("M-N" . term-send-backward-kill-word)
    ("<C-backspace>" . term-send-backward-kill-word)
    ("M-r" . term-send-reverse-search-history)
    ("M-d" . term-send-delete-word)
    ("M-," . term-send-raw)
    ("M-." . comint-dynamic-complete))
  "The key alist that will need to be bind.
If you do not like default setup, modify it, with (KEY . COMMAND) format."
  :type 'alist
  :group 'multi-term)

(defcustom multi-term-dedicated-window-height 14
  "The height of `multi-term' dedicated window."
  :type 'integer
  :group 'multi-term)

(defcustom multi-term-dedicated-max-window-height 30
  "The max height limit of `multi-term' dedicated window.
Default, when hide `multi-term' dedicated window, will remember
window height before hide, except height is larger than this.`"
  :type 'integer
  :group 'multi-term)

(defcustom multi-term-dedicated-skip-other-window-p nil
  "Default, can have `other-window' select window in cyclic ordering of windows.
In cases you don't want to select `multi-term' dedicated window, use `other-window'
and make `multi-term' dedicated window as a viewable sidebar.

So please turn on this option if you want to skip `multi-term' dedicated window with `other-window'.

Default is nil."
  :type 'boolean
  :set (lambda (symbol value)
         (set symbol value)
         ;; ad-advised-definition-p no longer exists on Emacs 24.4 as of 2014-01-03.
         (when (fboundp 'multi-term-dedicated-handle-other-window-advice)
           (if (fboundp 'ad-advised-definition-p)
               (when (ad-advised-definition-p 'other-window)
                 (multi-term-dedicated-handle-other-window-advice value))
             (when (ad-is-advised 'other-window)
               (multi-term-dedicated-handle-other-window-advice value)))))
  :group 'multi-term)

(defcustom multi-term-dedicated-select-after-open-p nil
  "Default, multi-term won't focus terminal window after you open dedicated window.
Please make this option with t if you want focus terminal window.

Default is nil."
  :type 'boolean
  :group 'multi-term)

(defcustom multi-term-dedicated-close-back-to-open-buffer-p nil
  "Some userlike the cursor return to the position it was before I opened the dedicated terminal window.
Please make this option with t if you want it. ;)

Default is nil."
  :type 'boolean
  :group 'multi-term
  )

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Constant ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defconst multi-term-dedicated-buffer-name "MULTI-TERM-DEDICATED"
  "The buffer name of dedicated `multi-term'.")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Variable ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defvar multi-term-dedicated-window nil
  "The dedicated `multi-term' window.")

(defvar multi-term-dedicated-buffer nil
  "The dedicated `multi-term' buffer.")

(defvar multi-term-dedicated-close-buffer nil
  "The buffer that first time open dedicated `multi-term' buffer.
Details look option `multi-term-dedicated-close-back-to-open-buffer-p'.")

(defvar multi-term-buffer-list nil
  "The list of non-dedicated terminal buffers managed by `multi-term'.")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Interactive Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;###autoload
(defun multi-term ()
  "Create new term buffer.
Will prompt you shell name when you type `C-u' before this command."
  (interactive)
  (let (term-buffer)
    ;; Set buffer.
    (setq term-buffer (multi-term-get-buffer current-prefix-arg))
    (setq multi-term-buffer-list (nconc multi-term-buffer-list (list term-buffer)))
    (set-buffer term-buffer)
    ;; Internal handle for `multi-term' buffer.
    (multi-term-internal)
    ;; Switch buffer
    (switch-to-buffer term-buffer)))

;;;###autoload
(defun multi-term-next (&optional offset)
  "Go to the next term buffer.
If OFFSET is `non-nil', will goto next term buffer with OFFSET."
  (interactive "P")
  (multi-term-switch 'NEXT (or offset 1)))

;;;###autoload
(defun multi-term-prev (&optional offset)
  "Go to the previous term buffer.
If OFFSET is `non-nil', will goto previous term buffer with OFFSET."
  (interactive "P")
  (multi-term-switch 'PREVIOUS (or offset 1)))

;;;###autoload
(defun multi-term-dedicated-open ()
  "Open dedicated `multi-term' window.
Will prompt you shell name when you type `C-u' before this command."
  (interactive)
  (if (not (multi-term-dedicated-exist-p))
      (let ((current-window (selected-window)))
        (if (multi-term-buffer-exist-p multi-term-dedicated-buffer)
            (unless (multi-term-window-exist-p multi-term-dedicated-window)
              (multi-term-dedicated-get-window))
          ;; Set buffer.
          (setq multi-term-dedicated-buffer (multi-term-get-buffer current-prefix-arg t))
          (set-buffer (multi-term-dedicated-get-buffer-name))
          ;; Get dedicate window.
          (multi-term-dedicated-get-window)
          ;; Whether skip `other-window'.
          (multi-term-dedicated-handle-other-window-advice multi-term-dedicated-skip-other-window-p)
          ;; Internal handle for `multi-term' buffer.
          (multi-term-internal))
        (set-window-buffer multi-term-dedicated-window (get-buffer (multi-term-dedicated-get-buffer-name)))
        (set-window-dedicated-p multi-term-dedicated-window t)
        ;; Select window.
        (select-window
         (if multi-term-dedicated-select-after-open-p
             ;; Focus dedicated terminal window if option `multi-term-dedicated-select-after-open-p' is enable.
             multi-term-dedicated-window
           ;; Otherwise focus current window.
           current-window)))
    (message "`multi-term' dedicated window has exist.")))

(defun multi-term-dedicated-close ()
  "Close dedicated `multi-term' window."
  (interactive)
  (if (multi-term-dedicated-exist-p)
      (let ((current-window (selected-window)))
        ;; Remember height.
        (multi-term-dedicated-select)
        (multi-term-dedicated-remember-window-height)
        ;; Close window.
        (if (and (require 'ecb nil t)
                 ecb-activated-window-configuration)
            ;; Toggle ECB window when ECB window activated.
            (progn
              (ecb-deactivate)
              (ecb-activate))
          ;; Otherwise delete dedicated window.
          (delete-window multi-term-dedicated-window)
          (if (multi-term-window-exist-p current-window)
              (select-window current-window))))
    (message "`multi-term' window is not exist.")))

(defun multi-term-dedicated-remember-window-height ()
  "Remember window height."
  (let ((win-height (multi-term-current-window-take-height)))
    (if (and (multi-term-dedicated-window-p) ;in `multi-term' window
             (> win-height 1)
             (<= win-height multi-term-dedicated-max-window-height))
        (setq multi-term-dedicated-window-height win-height))))

;;;###autoload
(defun multi-term-dedicated-toggle ()
  "Toggle dedicated `multi-term' window."
  (interactive)
  (if (multi-term-dedicated-exist-p)
      (progn
        (multi-term-dedicated-close)
        (if (and multi-term-dedicated-close-back-to-open-buffer-p
                 multi-term-dedicated-close-buffer)
            (switch-to-buffer multi-term-dedicated-close-buffer)
          ))
    (if multi-term-dedicated-close-back-to-open-buffer-p
        (setq multi-term-dedicated-close-buffer (current-buffer)))
    (multi-term-dedicated-open)
    ))

;;;###autoload
(defun multi-term-dedicated-select ()
  "Select the `multi-term' dedicated window."
  (interactive)
  (if (multi-term-dedicated-exist-p)
      (select-window multi-term-dedicated-window)
    (message "`multi-term' window is not exist.")))

(defun term-send-esc ()
  "Send ESC in term mode."
  (interactive)
  (term-send-raw-string "\e"))

(defun term-send-return ()
  "Use term-send-raw-string \"\C-m\" instead term-send-input.
Because term-send-input have bug that will duplicate input when you C-a and C-m in terminal."
  (interactive)
  (term-send-raw-string "\C-m")
  )

(defun term-send-backward-kill-word ()
  "Backward kill word in term mode."
  (interactive)
  (term-send-raw-string "\C-w"))

(defun term-send-forward-kill-word ()
  "Kill word in term mode."
  (interactive)
  (term-send-raw-string "\ed"))

(defun term-send-backward-word ()
  "Move backward word in term mode."
  (interactive)
  (term-send-raw-string "\eb"))

(defun term-send-forward-word ()
  "Move forward word in term mode."
  (interactive)
  (term-send-raw-string "\ef"))

(defun term-send-reverse-search-history ()
  "Search history reverse."
  (interactive)
  (term-send-raw-string "\C-r"))

(defun term-send-delete-word ()
  "Delete word in term mode."
  (interactive)
  (term-send-raw-string "\ed"))

(defun term-send-quote ()
  "Quote the next character in term-mode.
Similar to how `quoted-insert' works in a regular buffer."
  (interactive)
  (term-send-raw-string "\C-v"))

(defun term-send-M-x ()
  "Type M-x in term-mode."
  (interactive)
  (term-send-raw-string "\ex"))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Utilise Functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defun multi-term-internal ()
  "Internal handle for `multi-term' buffer."
  ;; Add customize keystroke with `term-mode-hook'
  (remove-hook 'term-mode-hook 'multi-term-keystroke-setup)
  (add-hook 'term-mode-hook 'multi-term-keystroke-setup)
  ;; Load term mode
  (term-mode)
  (term-char-mode)
  ;; Handle term buffer close
  (multi-term-handle-close)
  ;; Handle `output' variable.
  (setq term-scroll-show-maximum-output multi-term-scroll-show-maximum-output
        term-scroll-to-bottom-on-output multi-term-scroll-to-bottom-on-output)
  ;; Add hook to be sure `term' quit subjob before buffer killed.
  (add-hook 'kill-buffer-hook 'multi-term-kill-buffer-hook))

(defun multi-term-get-buffer (&optional special-shell dedicated-window)
  "Get term buffer.
If option SPECIAL-SHELL is `non-nil', will use shell from user input.
If option DEDICATED-WINDOW is `non-nil' will create dedicated `multi-term' window ."
  (with-temp-buffer
    (let ((shell-name (or multi-term-program ;shell name
                          (getenv "SHELL")
                          (getenv "ESHELL")
                          "/bin/sh"))
          (index 1)                     ;setup new term index
          term-name)                    ;term name
      (if dedicated-window
          (setq term-name multi-term-dedicated-buffer-name)
        ;; Compute index.
        (while (buffer-live-p (get-buffer (format "*%s<%s>*" multi-term-buffer-name index)))
          (setq index (1+ index)))
        ;; switch to current local directory,
        ;; if in-existence, switch to `multi-term-default-dir'.
        (cd (or default-directory (expand-file-name multi-term-default-dir)))
        ;; adjust value N when max index of term buffer is less than length of term list
        (setq term-name (format "%s<%s>" multi-term-buffer-name index)))
      ;; Try get other shell name if `special-shell' is non-nil.
      (if special-shell
          (setq shell-name (read-from-minibuffer "Run program: " shell-name)))
      ;; Make term, details to see function `make-term' in `term.el'.
      (if multi-term-program-switches
          (make-term term-name shell-name nil multi-term-program-switches)
        (make-term term-name shell-name)))))


(defun multi-term-handle-close ()
  "Close current term buffer when `exit' from term buffer."
  (when (ignore-errors (get-buffer-process (current-buffer)))
    (set-process-sentinel (get-buffer-process (current-buffer))
                          (lambda (proc change)
                            (when (string-match "\\(finished\\|exited\\)" change)
                              (kill-buffer (process-buffer proc)))))))

(defun multi-term-kill-buffer-hook ()
  "Function that hook `kill-buffer-hook'."
  (when (eq major-mode 'term-mode)
    ;; Quit the current subjob
    ;; when have alive process with current term buffer.
    ;; Must do this job BEFORE `multi-term-switch-after-close' action.
    (when (term-check-proc (current-buffer))
      ;; Quit sub-process.
      (term-quit-subjob))
    ;; Remember dedicated window height.
    (multi-term-dedicated-remember-window-height)
    (let ((killed-buffer (current-buffer)))
      ;; Try to switch other multi-term buffer
      ;; when option `multi-term-switch-after-close' is non-nil.
      (when multi-term-switch-after-close
        (multi-term-switch-internal multi-term-switch-after-close 1))
      ;; Remove killed buffer from the buffer list if it's in there
      (setq multi-term-buffer-list
            (delq killed-buffer multi-term-buffer-list)))))

(defun multi-term-switch (direction offset)
  "Switch `multi-term' buffers.
If DIRECTION is `NEXT', switch to the next term.
If DIRECTION `PREVIOUS', switch to the previous term.
Option OFFSET for skip OFFSET number term buffer."
  (unless (multi-term-switch-internal direction offset)
    (if multi-term-try-create
        (progn
          (multi-term)
          (message "Created a new `multi-term' buffer."))
      (message "No `multi-term' buffers exist."))))

(defun multi-term-switch-internal (direction offset)
  "Internal `multi-term' buffers switch function.
If DIRECTION is `NEXT', switch to the next term.
If DIRECTION `PREVIOUS', switch to the previous term.
Option OFFSET for skip OFFSET number term buffer."
  (if multi-term-buffer-list
      (let ((buffer-list-len (length multi-term-buffer-list))
            (my-index (cl-position (current-buffer) multi-term-buffer-list)))
        (if my-index
            (let ((target-index (if (eq direction 'NEXT)
                                    (mod (+ my-index offset) buffer-list-len)
                                  (mod (- my-index offset) buffer-list-len))))
              (switch-to-buffer (nth target-index multi-term-buffer-list)))
          (switch-to-buffer (car multi-term-buffer-list))))
    nil))

(defun multi-term-keystroke-setup ()
  "Keystroke setup of `term-char-mode'.

By default, the key bindings of `term-char-mode' conflict with user's keystroke.
So this function unbinds some keys with `term-raw-map',
and binds some keystroke with `term-raw-map'."
  (let (bind-key bind-command)
    ;; Unbind base key that conflict with user's keys-tokes.
    (cl-dolist (unbind-key term-unbind-key-list)
      (cond
       ((stringp unbind-key) (setq unbind-key (read-kbd-macro unbind-key)))
       ((vectorp unbind-key) nil)
       (t (signal 'wrong-type-argument (list 'array unbind-key))))
      (define-key term-raw-map unbind-key nil))
    ;; Add some i use keys.
    ;; If you don't like my keystroke,
    ;; just modified `term-bind-key-alist'
    (cl-dolist (element term-bind-key-alist)
      (setq bind-key (car element))
      (setq bind-command (cdr element))
      (cond
       ((stringp bind-key) (setq bind-key (read-kbd-macro bind-key)))
       ((vectorp bind-key) nil)
       (t (signal 'wrong-type-argument (list 'array bind-key))))
      (define-key term-raw-map bind-key bind-command))))

(defun multi-term-dedicated-handle-other-window-advice (activate)
  "Handle advice for function `other-window'.
If ACTIVATE is `non-nil', will enable advice
`multi-term-dedicated-other-window-advice'.
Otherwise, disable it."
  (if activate
      (ad-enable-advice 'other-window 'after 'multi-term-dedicated-other-window-advice)
    (ad-disable-advice 'other-window 'after 'multi-term-dedicated-other-window-advice))
  (ad-activate 'other-window))

(defun multi-term-current-window-take-height (&optional window)
  "Return the height the `window' takes up.
Not the value of `window-height', it returns usable rows available for WINDOW.
If `window' is nil, get current window."
  (let ((edges (window-edges window)))
    (- (nth 3 edges) (nth 1 edges))))

(defun multi-term-dedicated-get-window ()
  "Get `multi-term' dedicated window."
  (setq multi-term-dedicated-window
        (split-window
         (selected-window)
         (- (multi-term-current-window-take-height) multi-term-dedicated-window-height))))

(defun multi-term-dedicated-get-buffer-name ()
  "Get the buffer name of `multi-term' dedicated window."
  (format "*%s*" multi-term-dedicated-buffer-name))

(defun multi-term-dedicated-exist-p ()
  "Return `non-nil' if `multi-term' dedicated window exist."
  (and (multi-term-buffer-exist-p multi-term-dedicated-buffer)
       (multi-term-window-exist-p multi-term-dedicated-window)))

(defun multi-term-window-exist-p (window)
  "Return `non-nil' if WINDOW exist.
Otherwise return nil."
  (and window (window-live-p window)))

(defun multi-term-buffer-exist-p (buffer)
  "Return `non-nil' if `BUFFER' exist.
Otherwise return nil."
  (and buffer (buffer-live-p buffer)))

(defun multi-term-dedicated-window-p ()
  "Return `non-nil' if current window is `multi-term' dedicated window.
Otherwise return nil."
  (equal (multi-term-dedicated-get-buffer-name) (buffer-name (window-buffer))))

(defun multi-term-window-dedicated-only-one-p ()
  "Only have one non-dedicated window."
  (interactive)
  (let ((window-number 0)
        (dedicated-window-number 0))
    (walk-windows
     (lambda (w)
       (with-selected-window w
         (cl-incf window-number)
         (if (window-dedicated-p w)
             (cl-incf dedicated-window-number)))))
    (if (and (> dedicated-window-number 0)
             (= (- window-number dedicated-window-number) 1))
        t nil)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Advice ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defadvice delete-other-windows (around multi-term-delete-other-window-advice activate)
  "This is advice to make `multi-term' avoid dedicated window deleted.
Dedicated window can't deleted by command `delete-other-windows'."
  (let ((multi-term-dedicated-active-p (multi-term-window-exist-p multi-term-dedicated-window)))
    (if multi-term-dedicated-active-p
        (let ((current-window (selected-window)))
          (cl-dolist (win (window-list))
            (when (and (window-live-p win)
                       (not (eq current-window win))
                       (not (window-dedicated-p win)))
              (delete-window win))))
      ad-do-it)))

(defadvice delete-window (before multi-term-delete-window-advice activate)
  "Use `delete-window' delete `multi-term' dedicated window.
Have same effect as command `multi-term-dedicated-close'.
This advice to remember `multi-term' dedicated window height before deleting."
  ;; Remember window height before deleted.
  (multi-term-dedicated-remember-window-height))

(defadvice pop-to-buffer (before multi-term-pop-to-buffer-advice activate)
  "This advice fix the problem between `pop-to-buffer' and dedicated window.
By default, function `display-buffer' can't display buffer in selected window
if current window is `dedicated'.

So function `display-buffer' conflicts with `sr-speedbar' window, because
`sr-speedbar' window is a `dedicated' window.

That is to say, when current frame just have one `non-dedicated' window,
any functions that uses `display-buffer' can't split windows
to display buffer, even when the option `pop-up-windows' is enabled.

And the example function that can induce the problem is `pop-to-buffer'.

This advice will fix this problem when current frame just have one `non-dedicated' window."
  (when (and pop-up-windows                           ;`pop-up-windows' is enable
             (multi-term-window-dedicated-only-one-p) ;just have one `non-dedicated' window.
             (multi-term-window-exist-p multi-term-dedicated-window)
             (not (multi-term-dedicated-window-p))) ;not in `sr-speedbar' window
    (split-window-vertically)
    (windmove-down)))

(defadvice other-window (after multi-term-dedicated-other-window-advice)
  "Default, can use `other-window' select window in cyclic ordering of windows.
But sometimes we don't want to select `sr-speedbar' window,
but use `other-window' and just make `multi-term' dedicated
window as a viewable sidebar.

This advice can make `other-window' skip `multi-term' dedicated window."
  (let ((count (or (ad-get-arg 0) 1)))
    (when (and (multi-term-window-exist-p multi-term-dedicated-window)
               (eq multi-term-dedicated-window (selected-window)))
      (other-window count))))

(provide 'multi-term)

;; Local Variables:
;; time-stamp-line-limit: 10
;; time-stamp-start: "Last-Updated: <"
;; time-stamp-end: ">"
;; End:

;;; multi-term.el ends here

;;; LocalWords:  multi el dir sr Hawley eb ef cd