Emacs builtin mode 功能介绍

最近正在琢磨 Emacs 里的终端怎么用比较好,这次更新太涨姿势了!

施工完成了!

3 个赞

题外话:一般上一条命令 !!,例子: sudo !!,上一命令最后一个参数:!$,更多见:高效使用 Linux 命令行

官方文档靠后的一些章节实用性比较高,比如

  • Dealing with Emacs Trouble
  • Abbrevs
  • Merging Files with Emerge
  • Customizing VC
  • Maintaining Large Programs

里面的内容也很充实,就是感觉文字有些多(看不动。。),需要个班长来给我们划重点,感觉楼主可以胜任。:smile:

1 个赞

想起来李杀有一篇介绍 shell, term 和 eshell 的文章:

Emacs: Difference between shell, term, eshell

1 个赞

学习了,不过 !$ 按起来有点不太方便,!!还可以接受。

记下了!

扫了一遍,发现只是蜻蜓点水了一下……

我搞了好久才发现char-mode用term-raw-map,line-mode用term-mode-map,之前怎么bind key都不对劲。

对的,实际上 line-mode 不通用,只使用 char-mode 就好了。

我感觉 term-line-mode 可以改造一下,或者抄一下做一个新的 mode,启用的时候把 term 变成一个只读的普通 buffer,这样就可以用普通的快捷键浏览和复制东西了。

这个与 tmux 的那个浏览模式很像,我觉得可以再加个 copy-mode。如果是用 evil 的话那这个就显得有点多余了

evil-normal-state的时候char-mode会阻止光标移出当前命令的编辑区,要用line-mode才行。

(general-define-key
 :states '(insert emacs)
 :keymaps 'term-raw-map
 "<escape>" (lambda! (term-line-mode) (evil-motion-state)))
(general-define-key
 :states '(normal motion)
 :keymaps 'term-mode-map
 "a" (lambda! (evil-insert-state) (term-char-mode)))

mumu 的视频在哪呢?

参照楼主的教学,写了一个简单的配置。亮点:

  • my-term 命令:和 term 差不多,但不会在开启时要你选一下 shell 程序(还有一些细节,具体看代码)。

  • my-term-yank:可以直接在 char mode 下粘贴文本,比如用来粘贴复制来的命令或者路径。

  • my-term-browse-mode:把终端变成一个只读的、可以浏览的、键位和平时一样的 buffer,方便我们浏览一个很长的输出或者复制东西。

用法:把下面的代码复制到你的配置中,阅读并修改 “Example config" 部分。用 M-x my-term 启动终端。

(require 'term)

;;; term.el tweaks

(defvar my-term-escape-keys
  '("M-x")
  "Escape keys for `my-term'.
Notice that if you enable minor modes in the term, keys defined
by them override `term-raw-map', so they also behave like they
are escaped.")

(defun my-term-remove-escape-char ()
  "Undo changed by `term-set-escape-char'."
  (when term-escape-char
    (define-key term-raw-map term-escape-char 'term-send-raw)
    (setq term-escape-char nil)))

(defun my-term-setup-escape-keys ()
  "Set keys in `my-term-escape-keys' as escape chars in term."
  (dolist (key my-term-escape-keys)
    (define-key term-raw-map (kbd key) nil)))

(with-eval-after-load 'term
  ;; In term.el, `C-c' is explicitely escaped, in the top level scope, using
  ;; `term-set-escape-char'.  It's a useless API because it undoes its previous
  ;; call, so you can't escape multiple chars (`ansi-term' works around it
  ;; using a hack).  We undo it here because we have `my-term-escape-keys'
  ;; where you can specify more than 1 escaped keys.
  (my-term-remove-escape-char)
  (my-term-setup-escape-keys))

;;; `my-term' command

(defvar my-term-shell-program nil
  "Shell program for `my-term'.
We need this because we may use a lightweight shell like dash for
inferior shell, but want bash/zsh for interactive shell.")

(defun my-term ()
  "Start a terminal emulator in a new buffer.
This is like `term' but with several tweaks to make you happier."
  (interactive)
  (let ((prog (or my-term-shell-program
                  explicit-shell-file-name
                  (getenv "SHELL")
                  shell-file-name
                  (read-file-name "Shell executable: "
                                  "/" nil t)))
        (buf (generate-new-buffer "*term*")))
    ;; If the user calls `ansi-term' before, undo its call to
    ;; `term-set-escape-char'.
    (when term-escape-char
      (my-term-remove-escape-char)
      (my-term-setup-escape-keys))
    (with-current-buffer buf
      (term-mode)
      (term-exec buf (buffer-name) prog nil nil)
      (term-char-mode))
    (pop-to-buffer buf)))

;;; Helpers

(defvar my-term-browse-mode-map
  (make-sparse-keymap)
  "Keymap for `my-term-browse-mode'.")

(defun my-term-browse-mode ()
  "Turn the terminal buffer into a read-only normal buffer."
  (interactive)
  ;; Workaround: Without this code, there's a bug: Press `C-p' in char mode to
  ;; browse history, then `C-n' to go back, then `my-term-browse-mode', then
  ;; `C-n', you'll find a newline is produced.  Call `term-char-mode', that
  ;; newline is sent to the shell.  This is not a problem with
  ;; `my-term-browse-mode', since `term-line-mode' also has it.
  (let ((inhibit-read-only t))
    (save-excursion
      (goto-char (point-max))
      (while (eq (char-before) ?\n)
        (delete-char -1))))
  ;; Idea: We could put a `read-only' property to the region before
  ;; `process-mark', so current input could be edited, but I think there's
  ;; little benefit.
  (setq buffer-read-only t)
  (remove-hook 'pre-command-hook #'term-set-goto-process-mark t)
  (remove-hook 'post-command-hook #'term-goto-process-mark-maybe t)
  (use-local-map my-term-browse-mode-map))

(defun my-term-yank ()
  "Paste recent kill into terminal, in char mode."
  (interactive)
  (when-let ((text (current-kill 0))
             ;; Remove newlines at the beginning/end.))
             (text (string-trim text "\n+" "\n+")))
    (when (or (not (string-match-p "\n" text))
              (y-or-n-p "You are pasting a multiline string.  Continue? "))
      (term-send-raw-string text))))

;;; Example config

;; Set this to your shell program, or delete this if you just want to use the
;; inferior (reads "default") shell.
(setq my-term-shell-program "/bin/bash")

;; Keys you don't want them to be captured by the terminal.
(setq my-term-escape-keys '("C-c" "C-x" "M-x"))

;; Call this after you set `my-term-escape-keys'
(my-term-setup-escape-keys)

;; Bind keys to use in char mode.  Make sure you have the prefix key (`C-c' in
;; this example) in `my-term-escape-keys'.
(define-key term-raw-map (kbd "C-c b") 'my-term-browse-mode)
(define-key term-raw-map (kbd "C-M-v") 'my-term-yank)

;; Bind keys to use in browse mode.
(define-key my-term-browse-mode-map (kbd "C-c b") 'term-char-mode)

实际录到一半不想录了)

1 个赞

在 evil-collection 里也有个类似的实现,可以看一下

这个 my-term 是不是可以直接这样调用 (ansi-term my-term-shell-program) 就好了?或者直接用 shell-file-name 这个变量。

term.el 实际是有提供一个 paste 函数的 term-paste,似乎不用再自己写 my-term-yank 了。原来my-term-yank考虑了多行的情况)

在 evil-collection 里也有个类似的实现

我看到它是切换 char mode 和 line mode。我不想要 line mode,因为那个会用 term-mode-map,按回车还是发送命令。我只想要一个普通的只读 buffer,所以写了 my-term-browse-mode

这个 my-term 是不是可以直接这样调用 (ansi-term my-term-shell-program) 就好了?

是可以。写这个主要的原因是 term.el 自己 call 了一次 term-set-escape-char, ansi-term 自己又会 call 一次,然后把 C-xC-c 都 escape 了。我不想要它把 C-c 也 escape 掉,所以做了一些 hack,这样在 my-term-escape-keys 一处把想要 escape 的键位都设置好就行了。不纠结这个的话可以不用这个命令。

或者直接用 shell-file-name 这个变量。

我考虑的情况是 inferior shell 应该主要做非交互式使用。比如说有人可能希望 shell-command 之类的函数交给 dash,交互式的用 bash,所以提供了 my-term-shell-program 这个变量。

term.el 实际是有提供一个 paste 函数的 term-paste ,似乎不用再自己写 my-term-yank 了。

哈,我原来不知道,不过反正我写的比那个好用 :wink:

1 个赞

请问一下,ispell/aspell 哪个命令是将生词加入个人词库的?

因为ispell-personal-dictionary对应的文件格式是纯文本,所以可以直接编辑。

但是通常我是先通过 flyspell/ispell-region 发现哪里的单词有问题,然后再在那个单词上 M-x ispell-word (evil用户的话直接按 zm) 然后按i将单词插入至个人词典里的。可以 C-h f ispell-help 查看一下它完整功能。

:+1:感谢,根据你的指导搞定了。ispell-word i 好用。不是很喜欢flyspell 弹窗的用法。Emacs 还是不弹窗比较好,除了 company

project.el

内置的project管理,在master branch里有很多改进.对我来说比projectile还爽一点

比如有project-switch-project,可以通过completing-read来像图一样操作,

compl 示例

还有project-display-buffer等函数,对buffer的操作也不赖.

一个缺点就是不能和projectile一样根据cmake或者bazel等项目类型直接运行预设的命令,不过有project-shell,project-eshell,project-compile,如果shell有预设的alias,使用体验也不会差很多,而且更灵活.

另一个缺点:

因为是直接回溯找.git .svn等,所以不能处理symlink.

(vc-find-root dir ".git/")

1 个赞