代码分享:在 Eshell 实现 fzf 般查找历史命令

我的主力Shell 是Eshell, 但是平时我也会用Zsh, 而fzf 是一个非常好用的命令行工具,用了fzf 搜索命令: Peek 2017-12-16 10-29

回到 Eshell 下面,我觉得我应该也能实现类似的功能的, 因为我用的是Ivy/Counsel, 用一个counsel-esh-history 命令支持使用Ivy来搜索历史命令,但是不能像fzf 那样可以从用户输入获取命令的一部分,然后再进行过滤匹配,所以我自己拿了counsel-esh-history 的源码来修改了一下,改成了fzf 那样: Peek 2017-12-16 10-30

下面是源码:

(defun samray/esh-history ()
  "Interactive search eshell history."
  (interactive)
  (require 'em-hist)
  (save-excursion
    (let* ((start-pos (eshell-bol))
	   (end-pos (point-at-eol))
	   (input (buffer-substring-no-properties start-pos end-pos)))
      (let* ((command (ivy-read "Command: "
				(delete-dups
				 (when (> (ring-size eshell-history-ring) 0)
				   (ring-elements eshell-history-ring)))
				:preselect input
				:action #'ivy-completion-in-region-action))
	     (cursor-move (length command)))
	(kill-region (+ start-pos cursor-move) (+ end-pos cursor-move))
	)))
  ;; move cursor to eol
  (end-of-line)
  )

6 个赞

关于 Eshell 请教一个问题,怎么输入通配符?每次输入它都自动转化成文件名

也许 Eshell 不是给我这样的弱鸡使用的,每次使用总有种 shoot myself in the feet 的感觉。

有问題嗎?


用的 Spacemacs-base,沒开 shell layer。

然而我并不会出现通配符展开的问题:

Peek 2017-12-16 13-49 我猜,可能你是把eshell-cmpl-ignore-case 设置为 t了

那看来是 shell layer 的锅?

图片

没有啊

eshell-cmpl-expand-before-complete 呢?

图片

依然没有

不过我刚才看了一下 shell layer,确实是重度修改,还添加了许多不知道干啥用的插件

可能相关的配置:

(defun shell/init-eshell ()
  (use-package eshell
    :defer t
    :init
    (progn
      (spacemacs/register-repl 'eshell 'eshell)
      (setq eshell-cmpl-cycle-completions nil
            ;; auto truncate after 20k lines
            eshell-buffer-maximum-lines 20000
            ;; history size
            eshell-history-size 350
            ;; no duplicates in history
            eshell-hist-ignoredups t
            ;; buffer shorthand -> echo foo > #'buffer
            eshell-buffer-shorthand t
            ;; my prompt is easy enough to see
            eshell-highlight-prompt nil
            ;; treat 'echo' like shell echo
            eshell-plain-echo-behavior t
            ;; cache directory
            eshell-directory-name (concat spacemacs-cache-directory "eshell/"))

      (when shell-protect-eshell-prompt
        (add-hook 'eshell-after-prompt-hook 'spacemacs//protect-eshell-prompt))

      (autoload 'eshell-delchar-or-maybe-eof "em-rebind")

      (add-hook 'eshell-mode-hook 'spacemacs//init-eshell)
      (add-hook 'eshell-mode-hook 'spacemacs/disable-hl-line-mode))
    :config
    (progn

      ;; Work around bug in eshell's preoutput-filter code.
      ;; Eshell doesn't call preoutput-filter functions in the context of the eshell
      ;; buffer. This breaks the xterm color filtering when the eshell buffer is updated
      ;; when it's not currently focused.
      ;; To remove if/when fixed upstream.
      (defun [email protected] (fn process string)
        (let ((proc-buf (if process (process-buffer process)
                          (current-buffer))))
          (when proc-buf
            (with-current-buffer proc-buf
              (funcall fn process string)))))
      (advice-add
       #'eshell-output-filter
       :around
       #'[email protected])

      (require 'esh-opt)

      ;; quick commands
      (defalias 'eshell/e 'find-file-other-window)
      (defalias 'eshell/d 'dired)
      (setenv "PAGER" "cat")

      ;; support `em-smart'
      (when shell-enable-smart-eshell
        (require 'em-smart)
        (setq eshell-where-to-jump 'begin
              eshell-review-quick-commands nil
              eshell-smart-space-goes-to-end t)
        (add-hook 'eshell-mode-hook 'eshell-smart-initialize))

      ;; Visual commands
      (require 'em-term)
      (mapc (lambda (x) (push x eshell-visual-commands))
            '("el" "elinks" "htop" "less" "ssh" "tmux" "top"))

      ;; automatically truncate buffer after output
      (when (boundp 'eshell-output-filter-functions)
        (push 'eshell-truncate-buffer eshell-output-filter-functions))

      ;; These don't work well in normal state
      ;; due to evil/emacs cursor incompatibility
      (evil-define-key 'insert eshell-mode-map
        (kbd "C-k") 'eshell-previous-matching-input-from-input
        (kbd "C-j") 'eshell-next-matching-input-from-input))))

(defun shell/init-eshell-prompt-extras ()
  (use-package eshell-prompt-extras
    :commands epe-theme-lambda
    :init
    (setq eshell-highlight-prompt nil
          eshell-prompt-function 'epe-theme-lambda)))

(defun shell/init-eshell-z ()
  (use-package eshell-z
    :defer t
    :init
    (with-eval-after-load 'eshell
      (require 'eshell-z))))

其中的一个变量 shell-enable-smart-eshell 我这里设置为 nil

用过 neovim 的内嵌终端,就会觉着 Eshell 有点鸡肋的感觉。

像 fzf 这种需求根本不是事儿。 ๑乛ェ乛๑

1 个赞

求终端的字体 :smiley:

vim 用户又来砸场子 :joy:

额,身份被发现了吗?( ´◔ ‸◔’)

上面有很多终端截图,不知道你要哪位同学的截图呢?

这论坛如果回复上一楼的话是不会显示回复的是哪个贴的。比如现在这样。

所以默认回复的就是这个楼上

我没有用过 neovim, 比较好奇neovim 的内嵌终端,是ternimal emulator 还是 shell, Eshell 可以一个真正的Shell吖,除了top 这种强交互的命令没办法比较好滴处理之外,其他日常的命令都是用elisp 重写了的,这才是真正跨平台的shell ,最重要的是和Emacs 完美整合,即可以解析 shell 语法,可以解析 lisp 语法.只是文档少,用的人也不多

neovim 的内嵌终端和 eshell 应该都算 terminal emulator 吧?不太相信日常命令都用 elisp 重写了的这种做法。。。neovim 终端能应对 top 这种强交互的命令,因为它就是个 真正意义上的终端 ,和其它终端一模一样的……额……“终端”。

言尽于此。你何不自己花点时间尝试一下呢?

(defsubst eshell/ls (&rest args)
  "An alias version of `eshell-do-ls'."
  (let ((insert-func 'eshell-buffered-print)
	(error-func 'eshell-error)
	(flush-func 'eshell-flush))
    (apply 'eshell-do-ls args)))

(put 'eshell/ls 'eshell-no-numeric-conversions t)

(declare-function eshell-glob-regexp "em-glob" (pattern))

(defun eshell-do-ls (&rest args)
  "Implementation of \"ls\" in Lisp, passing ARGS."
  (funcall flush-func -1)
  ;; Process the command arguments, and begin listing files.
  (eshell-eval-using-options
   "ls" (if eshell-ls-initial-args
	    (list eshell-ls-initial-args args)
	  args)
   `((?a "all" nil show-all
	 "do not ignore entries starting with .")
     (?A "almost-all" nil show-almost-all
	 "do not list implied . and ..")
     (?c nil by-ctime sort-method
	 "sort by last status change time")
     (?d "directory" nil dir-literal
	 "list directory entries instead of contents")
     (?k "kilobytes" 1024 block-size
	 "using 1024 as the block size")
     (?h "human-readable" 1024 human-readable
	 "print sizes in human readable format")
     (?H "si" 1000 human-readable
	 "likewise, but use powers of 1000 not 1024")
     (?I "ignore" t ignore-pattern
	 "do not list implied entries matching pattern")
     (?l nil long-listing listing-style
	 "use a long listing format")
     (?n "numeric-uid-gid" nil numeric-uid-gid
	 "list numeric UIDs and GIDs instead of names")
     (?r "reverse" nil reverse-list
	 "reverse order while sorting")
     (?s "size" nil show-size
	 "print size of each file, in blocks")
     (?t nil by-mtime sort-method
	 "sort by modification time")
     (?u nil by-atime sort-method
	 "sort by last access time")
     (?x nil by-lines listing-style
	 "list entries by lines instead of by columns")
     (?C nil by-columns listing-style
	 "list entries by columns")
     (?L "dereference" nil dereference-links
	 "list entries pointed to by symbolic links")
     (?R "recursive" nil show-recursive
	 "list subdirectories recursively")
     (?S nil by-size sort-method
	 "sort by file size")
     (?U nil unsorted sort-method
	 "do not sort; list entries in directory order")
     (?X nil by-extension sort-method
	 "sort alphabetically by entry extension")
     (?1 nil single-column listing-style
	 "list one file per line")
     (nil "dired" nil dired-flag
	  "Here for compatibility with GNU ls.")
     (nil "help" nil nil
	  "show this usage display")
     :external "ls"
     :usage "[OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically across.")
   ;; setup some defaults, based on what the user selected
   (unless block-size
     (setq block-size eshell-ls-default-blocksize))
   (unless listing-style
     (setq listing-style 'by-columns))
   (unless args
     (setq args (list ".")))
   (let ((eshell-ls-exclude-regexp eshell-ls-exclude-regexp) ange-cache)
     (when ignore-pattern
       (unless (eshell-using-module 'eshell-glob)
	 (error (concat "-I option requires that `eshell-glob'"
			" be a member of `eshell-modules-list'")))
       (set-text-properties 0 (length ignore-pattern) nil ignore-pattern)
       (setq eshell-ls-exclude-regexp
	     (if eshell-ls-exclude-regexp
		 (concat "\\(" eshell-ls-exclude-regexp "\\|"
			 (eshell-glob-regexp ignore-pattern) "\\)")
	       (eshell-glob-regexp ignore-pattern))))
     ;; list the files!
     (eshell-ls-entries
      (mapcar (lambda (arg)
		(cons (if (and (eshell-under-windows-p)
			       (file-name-absolute-p arg))
			  (expand-file-name arg)
			arg)
		      (eshell-file-attributes
		       arg (if numeric-uid-gid 'integer 'string))))
	      args)
      t (expand-file-name default-directory)))
   (funcall flush-func)))

包括 ls cd cp ln mv rm pwd env man cat exit info time grep diff kill make ... 都用 elisp 重新实現/包装了。

是楼主的,第一张图

同学,你应该了解一下什么叫Shell, 什么叫 terminal emulator. 附上 Reddit 关于Eshell 的帖子一篇,里面有一句话说得非常好:

It’s important to understand that shells are not (or should not be) semantically bound to terminal emulator restrictions. Shells are a textual interface to the machine