Feeds:
Posts
Comments

Posts Tagged ‘ansi shell’

Gaz asks:

Is there an easy way to select (via buffer-name regex or similar) a subset of terminals in elisp, and then squirt the same command into each one?

Sure Gaz, that sounds fairly straightforward. The shell wrappers already stores the terminal buffers in sw-buffers. We just need to think of how we want to select the buffers. And it probably doesn’t come as any surprise that I’m going to use ido.

Ido provides regex matching by pressing C-t or defining ido-enable-regexp. It also has flex matching with ido-enable-flex-matching and you can continuously refine selections using C-SPC. It really is amazingly cool.

Surprisingly, I couldn’t find a straightforward way of accessing the interim matches. Nor is there a hook that executes at selection time. Instead, we will override RETURN. Dynamic scoping means we can define an effectively global variable, sw-buffer-matches to store the interim matches for later use. Then, we iterate over these matches and send the command using term-simple-send.

We only want RETURN to be overridden for this particular usage of ido, so we set the keymap on the ido-setup-hook.

(defun sw-ido-save-matches ()
  (interactive)
  (setq sw-buffer-matches ido-matches)
  (exit-minibuffer))

(defun sw-ido-my-keys ()
  (define-key ido-completion-map (kbd "RET") 'sw-ido-save-matches))

(defun sw-multi-cmd (&optional cmd)
  (interactive "sCommand: ")
  (let ((ido-setup-hook 'sw-ido-my-keys)
        (sw-buffer-matches ""))
    (ido-completing-read "choose buffers: " sw-buffers)
    (dolist (buffer sw-buffer-matches)
      (term-simple-send (sw-shell-get-process buffer) cmd))))

Read Full Post »

I spend a lot of time in the shell and I’ve never found a terminal emulator I really like. Konsole is okay, but the cut and paste is rubbish. PuTTY isn’t too bad either, but it doesn’t have tabs. Wouldn’t it be nice if there was something like PuTTY that gave me something similar to buffers in emacs?

You might be thinking that screen + PuTTY would work but that replaces some of the shortcuts I use all the time (C-a for example). Does eshell give me what I want? What about ansi-term?

Actually, ansi-term is pretty close to what I want. It also reconfigures some of the keys1 but I can easily fix that. So let me make a shortlist of the features I want:

  • fix the key bindings
  • get the mouse cut and paste working properly
  • Have a frame just for the shell
  • Have some kind of alias mapping to shells
  • Have multiple shells in different buffers

Let’s get started.


The #includes.

(require 'cl)
(require 'term)

Connecting to a remote host and a reasonable shell-like font.

(defconst sw-ssh "ssh -q -o StrictHostKeyChecking=no")
(defconst sw-shell-font "-*-courier new-*-r-*-*-18-*-*-*-*-*-iso8859-1")

Somewhere to store the frame for the shell and the buffers.

(defvar sw-frame nil)
(defvar sw-buffers nil)

Configuration variables: does the shell open in a name frame, and if the shell has exited for some reason do we keep it in the list?

(defvar sw-open-in-new-frame nil)
(defvar sw-remove-dead-terms t)

I like to build my software in layers. The lowest layer is the most primitive and provides functions to get processes if they exist and to kill buffers that don’t have a process.

(defun sw-shell-get-process (buffer-name)
  (let ((buffer (get-buffer (concat "*" buffer-name "*"))))
    (and (buffer-live-p buffer) (get-buffer-process buffer))))

(defun sw-get-process-if-live (buffer-name)
  (let ((proc (sw-shell-get-process buffer-name)))
    (and (processp proc)
         (equal (process-status proc) 'run)
         proc)))

(defun sw-kill-buffer-if-no-process (buffer-name)
  (let* ((buffer (get-buffer (concat "*" buffer-name "*")))
         (proc (sw-get-process-if-live buffer-name)))
    (when (and (not proc) (buffer-live-p buffer)) (kill-buffer buffer))))

(defalias 'sw-shell-exists-p 'sw-get-process-if-live)

The next layer provides two functions for wrapping basic buffer selection/creation. The plan is to ensure that there is a shell running in the buffer when the function finishes, but we don’t kill it if it exists already. Then there is another function that allows us to create or select a shell and send commands to it.

(defun sw-basic-shell (buffer-name)
  (sw-kill-buffer-if-no-process buffer-name)
  ;; If there is a process running, leave it, otherwise
  ;; create the new buffer
  (if (sw-shell-exists-p buffer-name)
      (message "Buffer already exists")
    (ansi-term "bash" buffer-name))
  (switch-to-buffer (concat "*" buffer-name "*")))

(defun sw-shell/commands (buffer-name &rest commands)
  (sw-basic-shell buffer-name)
  (let ((proc (sw-shell-get-process buffer-name)))
    (dolist (cmd commands)
      (term-simple-send proc cmd))))

This layer has methods for creating my standard shell locally (sw-standard-shell) and remotely (sw-server-login). I generally need to exec bash -l and su - after I’ve logged in. I could have done this in the .bashrc of course. You can adapt these start-up commands for your own usage.

(defun sw-standard-shell (buffer-name)
  (if (sw-shell-exists-p buffer-name)
      (switch-to-buffer (concat "*" buffer-name "*"))
    (sw-shell/commands buffer-name
                       "exec bash -l"
                       "su -")))

(defun sw-server-login (host &optional buffer-name)
  (setq buffer-name (or buffer-name host))
  (if (sw-shell-exists-p buffer-name)
      (switch-to-buffer (concat "*" buffer-name "*"))
    (sw-shell/commands buffer-name
                       (concat sw-ssh " " host)
                       "exec bash -l"
                       "su -")))

Set up my preferred colors and fonts.

(defun sw-set-display ()
  (interactive)
  (set-background-color "black")
  (set-foreground-color "orange")
  (set-frame-font sw-shell-font))

I need to override a few keys. This should all be fairly obvious.

(defun sw-set-keymap ()
  (term-set-escape-char ?\C-z)
  (define-key term-raw-map "\C-c" 'term-interrupt-subjob)
  (define-key term-raw-map "\C-y" 'yank)
  (define-key term-raw-map (kbd "\M-x") 'execute-extended-command)
  (define-key term-raw-map "\e" 'term-send-raw)
  (define-key term-raw-map (kbd "") 'scroll-down)
  (define-key term-raw-map (kbd "") 'scroll-up))

(sw-set-keymap)

I like highlighted regions to be automatically copied and to paste with the right-hand mouse button. This more-or-less works although it would need some tweaking to get it to send exactly the command you see on the screen.

;; Functions to get the mouse working more-or-less as I like it

(defun sw-mouse-paste-clipboard (click arg)
  (interactive "e\nP")
  (let ((proc (get-buffer-process (current-buffer))))
    (term-send-string proc (current-kill 0)))
  (setq deactivate-mark t))

(defun sw-mouse-copy-region-to-clipboard (click)
  (interactive "e")
  (mouse-set-region click)
  (let ((transient-mark-mode nil))
    (copy-region-as-kill (region-beginning) (region-end))))

(define-key term-raw-map
  [drag-mouse-1]
  'sw-mouse-copy-region-to-clipboard)

(define-key term-raw-map [mouse-3] 'sw-mouse-paste-clipboard)

Functions to read a buffer name from the user and select the shell-specific frame if we’ve set sw-open-in-new-frame.

(defun sw-read-buffer-name ()
  (when sw-remove-dead-terms
    ;; Remove dead terms before offering them to the user
    (setq sw-buffers (delete-if-not 'sw-shell-exists-p sw-buffers)))
  (let ((buffer-name
         (ido-completing-read
          "Choose buffer name: "
          sw-buffers)))

    (if (stringp buffer-name)
        buffer-name
      (error "Invalid buffer name"))))

(defun sw-get-buffer-proc ()
  (sw-get-process-if-live (sw-read-buffer-name)))

(defun sw-select-frame ()
  (if (not sw-open-in-new-frame)
      (sw-set-display)
    (unless (frame-live-p sw-frame)
      (setq sw-frame (make-frame))
      (select-frame-set-input-focus sw-frame)
      (sw-set-display))
    (select-frame-set-input-focus sw-frame)))

This actually selects the buffer, reading the buffer-name from the user if necessary.

(defun sw-choose-buffer (&optional buffer-name)
  (sw-select-frame)

  (unless (stringp buffer-name)
    (setq buffer-name (sw-read-buffer-name)))

  (unless (sw-shell-exists-p buffer-name)
    (sw-kill-buffer-if-no-process buffer-name)
    (setq sw-buffers (delete buffer-name sw-buffers)))

  (let ((already-existed t))
    (if (member buffer-name sw-buffers)
        (switch-to-buffer (concat "*" buffer-name "*"))
      (setq already-existed nil)
      (push buffer-name sw-buffers))

    (list buffer-name already-existed)))

And these are the top level commands – sw-open-shell and sw-open-remote-shell. They build on sw-standard-shell and sw-server-login respectively but read a buffer name and remote server name as appropriate.

(defun sw-open-shell (&optional buffer-name)
  (interactive)
  (multiple-value-bind (buffer-name already-existed)
      (sw-choose-buffer buffer-name)
    (unless already-existed
      (sw-standard-shell buffer-name))))

(defun sw-open-remote-shell (&optional buffer-name server-name)
  (interactive)
  (multiple-value-bind (buffer-name already-existed)
      (sw-choose-buffer buffer-name)
    (unless already-existed
      (unless (stringp server-name)
        (setq server-name (read-string "Server: " buffer-name)))
      (sw-server-login server-name buffer-name))))

(provide 'shell-wrappers)

And there we go. sw-shell/commands is generally useful for firing up shells and executing arbitrary commands. I use this all over the place.

Let me know if you have any tips or suggestions for improvement.

If you liked this post, why not subscribe to my RSS feed.


1.For example changing C-c to C-c C-c is very bad. One day I might need to kill that runaway rm -rf / command very urgently and maybe I’ll have forgotten that C-c is broken.

Read Full Post »

Follow

Get every new post delivered to your Inbox.