;;; multi-shell.el --- use escreen and term to manage multiple shells ;; Author: Mark Triggs ;; This file 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 2, or (at your option) ;; any later version. ;; This file 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 GNU Emacs; see the file COPYING. If not, write to ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;;; Commentary: ;;; Code: (defvar multi-shell-screen nil "The escreen screen number used for shell") (defun multi-shell-list () "The shell buffers presently active" (sort (remove-if-not (lambda (b) (string-match "^\*terminal" (buffer-name b))) (buffer-list)) (lambda (a b) (< (string-to-number (cadr (split-string (buffer-name a) "[<>]"))) (string-to-number (cadr (split-string (buffer-name b) "[<>]"))))))) (defun multi-shell-next () "Go to the next shell" (interactive) (multi-shell-switch 'NEXT)) (defun multi-shell-prev () "Go to the previous shell" (interactive) (multi-shell-switch 'PREVIOUS)) (defun multi-shell-switch (direction) "if direction is NEXT, switch to the next shell. If PREVIOUS, switch to the previous shell" (let ((shells (multi-shell-list))) (setf (cdr (last shells)) shells) (let ((this-buffer (position (current-buffer) (multi-shell-list)))) (if this-buffer (if (eql direction 'NEXT) (switch-to-buffer (nth (1+ this-buffer) shells)) (switch-to-buffer (nth (+ (1- (length (multi-shell-list))) this-buffer) shells))) (switch-to-buffer (car shells)))))) (defvar multi-shell-dir nil) (defun multi-shell () (interactive) (let ((dir default-directory)) (cond ((and multi-shell-screen (/= multi-shell-screen escreen-current-screen-number)) ;; a shell screen exists, but is not focused (escreen-goto-screen multi-shell-screen) (unless (string-match "terminal" (buffer-name (current-buffer))) (if (null (multi-shell-list)) (multi-shell-new dir) (pop-to-buffer (car (multi-shell-list))) (setq multi-shell-dir dir)))) (multi-shell-screen ;; a shell screen exists and is focused (if (or (string-match "terminal" (buffer-name (current-buffer))) (null (multi-shell-list))) (multi-shell-new (if multi-shell-dir (prog1 multi-shell-dir (setq multi-shell-dir nil)) dir)) (switch-to-buffer (car (multi-shell-list))))) (t (escreen-create-screen) (setq multi-shell-screen escreen-current-screen-number) (multi-shell-new dir))))) (defun multi-shell-new (&optional dir) (interactive) (let* ((nshells (length (multi-shell-list))) (n (if nshells (1+ nshells) 1)) (term-term-name "vt100") (term-buffer (with-temp-buffer (if dir (cd dir) (cd (expand-file-name "~/"))) (make-term (concat "terminal<" (format "%s" n) ">") (or (getenv "SHELL") "bash"))))) (set-buffer term-buffer) (term-mode) (term-char-mode) (when (ignore-errors (get-buffer-process (current-buffer))) (set-process-sentinel (get-buffer-process (current-buffer)) (lambda (proc change) (when (or (string-match "finished" change) (string-match "exited" change)) (escreen-goto-screen multi-shell-screen) (with-current-buffer (process-buffer proc) (multi-shell-prev)) (kill-buffer (process-buffer proc)))))) (setq term-scroll-show-maximum-output nil) (setq term-scroll-to-bottom-on-output nil) (switch-to-buffer term-buffer) (define-key term-raw-map [(control prior)] 'multi-shell-prev) (define-key term-raw-map [(control next)] 'multi-shell-next) (define-key term-raw-map [(shift prior)] 'scroll-down) (define-key term-raw-map [(shift next)] 'scroll-up) (define-key term-raw-map [(control d)] 'term-delchar-or-maybe-eof-mst) ;; erc connection tracking (define-key term-raw-map (kbd "M-`") (lookup-key (current-global-map) (kbd "M-`"))))) (defun multi-shell-select-or-create () "If the shell screen number is not selected, select it. Otherwise, run an new shell." (interactive) (if (or (not multi-shell-screen) (= escreen-current-screen-number multi-shell-screen)) (multi-shell-new default-directory) (escreen-goto-screen multi-shell-screen) (cond ((null (multi-shell-list)) (multi-shell-new (if (buffer-file-name) (file-name-directory (buffer-file-name)) nil))) ((not (string-match "terminal" (buffer-name (current-buffer)))) (switch-to-buffer (car (multi-shell-list))))))) (defun term-delchar-or-maybe-eof-mst () (interactive) (when (looking-at "\n") (delete-char 1)) (cond ((eobp) (kill-process (get-buffer-process (current-buffer))) (kill-buffer nil)) (t (term-send-raw-string "\C-d") (delete-char 1)))) (define-key global-map (kbd "C-c t") 'multi-shell) (add-hook 'term-load-hook (lambda () (interactive) (define-key term-mode-map [(control d)] 'term-delchar-or-maybe-eof-mst) (define-key term-mode-map [(control prior)] 'multi-shell-prev) (define-key term-mode-map [(control next)] 'multi-shell-next))) (provide 'multi-shell)