LoginSignup
7
8

More than 5 years have passed since last update.

eshell の強化

Last updated at Posted at 2019-02-08

背景

以前Emacs でなんでもやりたいと思ってから、zsh メインで使っていたのを eshell メインにした。eshell はデフォルトだと、補完などzsh に比べて劣るところ が多かったため、使えるレベルにするためにいくつか改良を加えた。

環境

  • Linux 4.19.2 (arch Linux)
  • GNU Emacs 26.1

current buffer でeshell を起動

eshell-on-current-dir

現在のバッファのディレクトリでeshell を起動する関数を作成した。 Eshell - Caiorss.GitHub.io by caiorss を参照にした。

(defun eshell-on-current-dir (&optional arg)
  "invoke eshell and cd to current directory"
  (interactive "P")
  (let ((dir default-directory))
    (eshell arg)
    (cd dir))
  (eshell-emit-prompt)
  (goto-char (point-max)))

popwin でopen

上記関数を実行する度に、eshell が1バッファ埋めてしまうのは不便のため、 eshell バッファはpopwin で開くことにした。設定は下記。

(add-to-list 'popwin:special-display-config
             '("\\`\\*eshell" :regexp t :dedicated t :position bottom
               :height 0.3))

補完強化

デフォルトでは、パスの通っているコマンド、カレントディレクトリ内の ファイル、ディレクトリしか補完できない。そのため、サブコマンドが必要なコマンド、 それ以外の補完候補を取るコマンドの補完をpcomplete で強化した。 下記3つのコマンドの補完関数を作成した。

  • sudo
  • systemctl
  • man

また、pcomplete の使い方に関しては、

を参照した。

;;;; sudo completion
(defun pcomplete/sudo ()
  "Completion rules for the `sudo' command."
  (let ((pcomplete-ignore-case t))
    (pcomplete-here (funcall pcomplete-command-completion-function))
    (while (pcomplete-here (pcomplete-entries)))))

;;;; systemctl completion
(defcustom pcomplete-systemctl-commands
  '("disable" "enable" "status" "start" "restart" "stop" "reenable"
    "list-units" "list-unit-files")
  "p-completion candidates for `systemctl' main commands"
  :type '(repeat (string :tag "systemctl command"))
  :group 'pcomplete)

(defvar pcomplete-systemd-units
  (split-string
   (shell-command-to-string
    "(systemctl list-units --all --full --no-legend;systemctl list-unit-files --full --no-legend)|while read -r a b; do echo \" $a\";done;"))
  "p-completion candidates for all `systemd' units")

(defvar pcomplete-systemd-user-units
  (split-string
   (shell-command-to-string
    "(systemctl list-units --user --all --full --no-legend;systemctl list-unit-files --user --full --no-legend)|while read -r a b;do echo \" $a\";done;"))
  "p-completion candidates for all `systemd' user units")

(defun pcomplete/systemctl ()
  "Completion rules for the `systemctl' command."
  (pcomplete-here (append pcomplete-systemctl-commands '("--user")))
  (cond ((pcomplete-test "--user")
         (pcomplete-here pcomplete-systemctl-commands)
         (pcomplete-here pcomplete-systemd-user-units))
        (t (pcomplete-here pcomplete-systemd-units))))

;;;; man completion
(defvar pcomplete-man-user-commands
  (split-string
   (shell-command-to-string
    "apropos -s 1 .|while read -r a b; do echo \" $a\";done;"))
  "p-completion candidates for `man' command")

(defun pcomplete/man ()
  "Completion rules for the `man' command."
  (pcomplete-here pcomplete-man-user-commands))

bookmarks でお気に入りのディレクトリ登録

zsh を使っていたときは、お気に入りのディレクトリをブックマークして すぐに移動できるプラグインを使用していた。 私はeshell でも同じことがしたく、ディレクトリにも使用できるということで、 emacs 標準のbookmarks 機能を利用することにした。そうしてeshell 上で動く bmk というコマンドを作った。機能としては

  • 登録済みのブックマークにジャンプ
  • カレントディレクトリのブックマーク登録
  • 特定のブックマークの削除
  • ブックマーク一覧の表示

である。使い方は bmk -h を入力して表示されるヘルプメッセージを参照してほしい。

;;; eshell bookmark command
(defun pcomplete/eshell-mode/bmk ()
  "Completion for `bmk'"
  (pcomplete-opt "adl")
  (pcomplete-here (bookmark-all-names)))

(defun show-bmk-list ()
  "Show the bookmark list and each corresponding path"
  (let (output-string bmk-name bmk-path (each-format "%-15s|%-5s|%s
"))
    (setq output-string (concat (format each-format "Bookmarks" "Type"
                                        "Path") (format "%s
" (make-string 80 ?-))))
    (dolist (tmp bookmark-alist)
      (setq bmk-name (car tmp))
      (setq bmk-path (abbreviate-file-name (assoc-default 'filename tmp)))
      (if (file-directory-p bmk-path)
          (setq output-string
                (concat output-string (format each-format bmk-name "D" bmk-path)))
        (setq output-string (concat output-string (format each-format bmk-name "F" bmk-path)))))
    output-string))

(defun add-bmk (&optional args)
  "add the current path to the bookmark as the name of the first argument"
  (let ((name (or (car args)
                  (file-name-nondirectory
                   (directory-file-name default-directory)))))
    (if name
        (progn
          (bookmark-set name)
          (bookmark-set-filename name (eshell/pwd))
          (bookmark-save)
          (format "Saved current directory in bookmark %s" name))
      (error "You must enter a bookmark name"))))

(defun delete-bmk (args)
  "delete bookmark arguments."
  (let (output-string)
    (if args
        (progn (dolist (tmp args)
                 (bookmark-delete tmp)
                 (bookmark-save)
                 (setq output-string
                       (concat output-string (format "Deleted the bookmark %s
" tmp))))
               output-string)
      (error "You must enter a bookmark name"))))

(defun cd-bmk (args)
  "cd/open the first bookmark argument"
  (let ((bookmark (car args)) filename)
    (if (setq filename (bookmark-get-filename bookmark))
        ;; If it points to a directory, change to it.
        (if (file-directory-p filename)
            (eshell/cd filename)
          ;; otherwise, just jump to the bookmark
          (bookmark-jump bookmark))
      (error "%s is not a bookmark" bookmark))))

;; to add bookmark on eshell
(defun bookmark-alternate-buffer-file-name ()
  "Return the current buffer's file in a way useful for bookmarks."
  ;; Abbreviate the path, both so it's shorter and so it's more
  ;; portable.  E.g., the user's home dir might be a different
  ;; path on different machines, but "~/" will still reach it.
  (abbreviate-file-name
   (cond
    (buffer-file-name buffer-file-name)
    ((and (boundp 'dired-directory) dired-directory)
     (if (stringp dired-directory)
         dired-directory
       (car dired-directory)))
    (default-directory)
    (t (error "Buffer not visiting a file or directory")))))

(advice-add 'bookmark-buffer-file-name :override
            'bookmark-alternate-buffer-file-name)


(defun eshell/bmk (&rest args)
  "Eshell bookmark command to show/add/delete bookmarks.
For usage, execute without arguments."
  (eshell-eval-using-options
   "bmk" args
   '((?a "add" t add
         "add current directory as the directory name or BOOKMARK")
     (?d "delete" t del
         "delete BOOKMARK entry")
     (nil "list" nil show
          "show the bookmark list")
     (?h "help" nil nil "show this usage screen")
     ;; :show-usage
     :usage "BOOKMARK
   or: bmk -a [BOOKMARK]
   or: bmk -d BOOKMARK ...
   or: bmk [OPTION]
Change directory pointed to by BOOKMARK
or bookmark-jump to the BOOKMARK if it is not a directory.
With an option, this can create a bookmark, delete bookmarks
or show the bookmark list.
Without any BOOKMARK and option, this shows the bookmark list.")
   (bookmark-maybe-load-default-file)
   (setq args (eshell-flatten-list args))
   (cond
    (show
     (show-bmk-list))
    (add
     (add-bmk args))
    (del
     (delete-bmk args))
    (args
     (cd-bmk args))
    (t (show-bmk-list)))))

まとめ

eshell の強化方法について載せた。補完に関してはpcomplete で強化はしているが マイナーコマンドの補完ができないといった不便なところもまだまだある。 使ったことはないが、fish の補完を実装できるという、 GitHub - Ambrevar/emacs-fish-completion をいずれ使ってみたい。 また最近は、dired でディレクトリ移動を行っているが、階層の遠いディレクトリ はbookmarks 機能のジャンプを使っている。ブックマークの登録は、そのディレクトリに 行ってから eshell-on-current-dir でeshell 上で bmk を使うと便利だ。

7
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
8