Emacs builtin mode 功能介绍

转移到github上了,图片链接还是使用的论坛的,更新日志也会在这个串下同步。


Changelog

  • 2020-03-19 新增加isearch的介绍

开这个串的原因主要是发现 Emacs 自带的 mode 有些也挺不错的,而一般初学者(例 如我)使用 Emacs 时间比较短,对它自身强大的 mode 不了解而错失一些可以提高生 产力的配置。因此有了这个串,可以方便查询,分享使用体验。

欢迎大家分享一下自己在使用 Emacs 自带 mode 的最佳实践呀!

winner-mode

winner-mode 是一个全局的 minor mode。它的主要功能是记录窗体的变动。例如当前有2 个窗口,然后你关了一个,这时可以通过 winner-undo 来恢复。还可以再 winner-redo 来撤销刚才的 undo.

它默认按键绑定为:

(C-c <Left>) winner-undo
(C-c <Right>) winner-redo

建议配置:

(use-package winner-mode
  :ensure nil
  :hook (after-init . winner-mode))

同时,它也可以应用在 ediff 上,恢复由 ediff 导致的窗体变动。

(use-package ediff
  :ensure nil
  :hook (ediff-quit . winner-undo)

saveplace

saveplace 记录了上次打开文件时 cursor 停留在第几行、第几列。

建议配置:

(use-package saveplace
  :ensure nil
  :hook (after-init . save-place-mode))

hl-line

高亮当前行。

(use-package hl-line
  :ensure nil
  :hook (after-init . global-hl-line-mode))

hideshow

隐藏、显示结构化数据,如 { } 里的内容。对于单函数较长的情况比较有用。

建议配置:

(use-package hideshow
  :ensure nil
  :diminish hs-minor-mode
  :bind (:map prog-mode-map
         ("C-c TAB" . hs-toggle-hiding)
         ("M-+" . hs-show-all))
  :hook (prog-mode . hs-minor-mode)
  :custom
  (hs-special-modes-alist
   (mapcar 'purecopy
           '((c-mode "{" "}" "/[*/]" nil nil)
             (c++-mode "{" "}" "/[*/]" nil nil)
             (rust-mode "{" "}" "/[*/]" nil nil)))))

一些类似 hideshow 的插件

其中 origami 有 lsp 支持版本 lsp-origami.

whitespace

显示空白字符,如 \t \v \v 空格等等。

可以配置在 prog-modemarkdown-modeconf-mode 下,显示行尾的空白字符。

(use-package whitespace
  :ensure nil
  :hook ((prog-mode markdown-mode conf-mode) . whitespace-mode)
  :config
  (setq whitespace-style '(face trailing)))

当然,仅显示行尾空白字符也可以简单地设置 show-trailing-whitespacet 来开启。

so-long

有时候会打开一些文件,这些文件里的某一行特别长,而 Emacs 没有针对这种情况做特 殊处理,会导致整个界面卡死。现在它来了!

直接全局启用:

(use-package so-long
  :ensure nil
  :config (global-so-long-mode 1))

当打开一个具有长行的文件时,它会自动检测并将一些可能导致严重性能的 mode 关闭, 如 syntax highlight

注: Emacs 27+ 自带

autorevert

有时候Emacs里打开的文件可能被外部修改,启用autorevert的话可以自动更新对应的 buffer.

(use-package autorevert
  :ensure nil
  :hook (after-init . global-auto-revert-mode))

isearch

本身Emacs自带的isearch已经足够强大,稍加修改就可以增加实用性。

例如anzu的显示匹配个数的功能就已经原 生支持了。通过

(setq isearch-lazy-count t
      lazy-count-prefix-format "%s/%s ")

来显示如 10/100 这种状态。

比较恼人的一点是,在搜索中删除字符会回退搜索结果,而不是停在当前位置将最后一个搜 索字符删除。这里可以通过remap isearch-delete-char来实现。

此外,还可以将搜索结果保持在高亮状态以方便肉眼识别。这个是通过设置 lazy-highlight-cleanupnil实现的。去除高亮状态需要人工M-x调用 lazy-highlight-cleanup

(use-package isearch
  :ensure nil
  :bind (:map isearch-mode-map
         ([remap isearch-delete-char] . isearch-del-char))
  :custom
  (isearch-lazy-count t)
  (lazy-count-prefix-format "%s/%s ")
  (lazy-highlight-cleanup nil))

效果图:

注:isearch-lazy-countlazy-count-prefix-format需要Emacs 27+

34赞

楼主加油,多整理一些常用或者入门的功能出来,带更多的人入教 :smiley: :smiley: :smiley:

1赞

delete-selection-mode,选中文本后可以直接输入,省去了删除操作

whitespace 的功能其实挺多的,我的配置:

(use-package whitespace
  :hook (after-init . global-whitespace-mode)
  :config
  ;; Don't use different background for tabs.
  (face-spec-set 'whitespace-tab
                 '((t :background unspecified)))
  ;; Only use background and underline for long lines, so we can still have
  ;; syntax highlight.

  ;; For some reason use face-defface-spec as spec-type doesn't work.  My guess
  ;; is it's due to the variables with the same name as the faces in
  ;; whitespace.el.  Anyway, we have to manually set some attribute to
  ;; unspecified here.
  (face-spec-set 'whitespace-line
                 '((((background light))
                    :background "#d8d8d8" :foreground unspecified
                    :underline t :weight unspecified)
                   (t
                    :background "#404040" :foreground unspecified
                    :underline t :weight unspecified)))

  ;; Use softer visual cue for space before tabs.
  (face-spec-set 'whitespace-space-before-tab
                 '((((background light))
                    :background "#d8d8d8" :foreground "#de4da1")
                   (t
                    :inherit warning
                    :background "#404040" :foreground "#ee6aa7")))

  (setq
   whitespace-line-column nil
   whitespace-style
   '(face             ; visualize things below:
     empty            ; empty lines at beginning/end of buffer
     lines-tail       ; lines go beyond `fill-column'
     space-before-tab ; spaces before tab
     trailing         ; trailing blanks
     tabs             ; tabs (show by face)
     tab-mark         ; tabs (show by symbol)
     )))

比较好的是能指示过长的行,这样都不需要装那种显示一条竖线的插件了。

再介绍一个:

subword

(use-package subword
  :hook (after-init . global-subword-mode))

打开这个 mode 以后就能正确地处理驼峰命名中的单词了。

8赞

我突然我现我没法直接编辑我的主题了,只能更新在下面了。


Changelog

  • 2020-04-01 添加了tempo的介绍、使用
  • 2020-04-03 添加了align-regexp的使用场景

tempo

tempo可以算是yasnippet的祖先, skeleton算是它的爷爷。由于tempo里可以使用elisp函数,灵活性非常大。

实际上在写代码的时候,想插入一个LICENSE头是个比较常用的需求,它也可以通过其 他方式如auto-insert在打开文件时就自动插入。在这里,我们使用tempo来实现。

目前比较推荐的方式是采用SPDX的格式,而不是直接把license内容写入代码文件中。 采用SPDX格式可以有效的减少文件大小,不会喧宾夺主占用大量代码行数。

一个典型的license头是这样:

// Copyright 2017 - 2018 ccls Authors
// SPDX-License-Identifier: Apache-2.0

所以我们可以仿照着这个格式来写一个tempotemplate.

;; 完整的列表非常长,可以访问 https://spdx.org/licenses/ 获得
(defconst license-spdx-identifiers
  '(Apache-1.0 Apache-2.0 MIT))

(tempo-define-template "license"
  '(comment-start
    (format "Copyright %s - present %s Authors"
            (format-time-string "%Y")
            (if (featurep 'projectile)
                (progn
                  (require 'projectile)
                  (projectile-project-name))
              "Unknown"))
    comment-end > n>
    comment-start
    "SPDX-License-Identifier: " (completing-read "License: "
                                                 license-spdx-identifiers)
    comment-end > n>)
  'license
  "Insert a SPDX license.")

tempo内的>表示的是缩进,n表示的是插入一个换行,其他的部分就是一个普通的 elisp函数了。

这样定义了这个template之类,会生成一个叫tempo-template-license的函数。因此我 们可以直接调用它来插入license头部。

此外还可以结合abbrev-mode来自动替换,如果想在elisp-mode下直接替代,可以通过 define-abbrev来实现:

(define-abbrev emacs-lisp-mode-abbrev-table ";license" "" 'tempo-template-license)

这里只需要在elisp-mode下开启abbrev-mode,然后输入;license 就会实现自动替换 (注意,最后要有一个空格)。

align

听说有些写java的朋友特别喜欢将变量的=对齐,即原来的代码是这样的:

private int magicNumber = 0xdeadbeef;
private double PI = 3.14159265358939723846264;

选中它们,然后调用align-regexp,给定=作为它的参数,就会将上述代码的=部分对 齐了。

private int magicNumber = 0xdeadbeef;
private double PI       = 3.14159265358939723846264;

其他align相关的函数功能还有待开发。

2赞

我尝试配置过长的行,但是没有效果,还有别的设置吗?

你怎么配置的?在 emacs -Q 里执行的话有效果吗?

好像没生效,我是在 spacemacs 里面加的,是在 user-config 里面吗?

我忘记 spacemacs 怎么写用户配置了。。。我试了我的配置在 emacs -Q 里是能生效的,你需要保证你的代码被执行了,而且没有和 spacemacs 里别的配置冲突。

(use-package whitespace
  :ensure nil
  :hook ((after-init . global-whitespace-mode)
         (before-save . (lambda () (progn
                                       (untabify (point-min) (point-max))
                                       (whitespace-cleanup))))))

这么做能在保存时自动清理空白, 但是在makefile等以tab为标识的文件中也会将tab转换为空格, 怎么排除特定的mode呢

(setq whitespace-global-modes '(not makefile-mode))

:sweat_smile: tab 转 空格是 (untabify (point-min) (point-max)) 因为都和空白有关我就放到一起了

Changelog

  • 2020-04-12 isearch 模拟浏览器搜索操作

使 isearch 工作得像在浏览器里搜索一样

在浏览器里,我们只需要按C-f,然后敲入所要搜索的字符串。之后只要按回车就可以不 断地向下搜索。如果我们需要向上搜索,那么需要点击一下向上的箭头。

现在我们在isearch里模拟这种情况,还是使用C-s来调用isearch。但是之后的 repeat操作是交给了回车。

首先,我们先定义一下变量来保存当前搜索的方向。

(defvar my/isearch--direction nil)

然后使得isearch-mode-map下的C-s可以告诉我们当前是在向下搜索;同理,使得 isearch-mode-map下的C-r告诉我们是在向上搜索。

(define-advice isearch-repeat-forward (:after (_))
  (setq-local my/isearch--direction 'forward))
(define-advice isearch-repeat-backward (:after (_))
  (setq-local my/isearch--direction 'backward))

这里偷懒,采用了advise的方式。如果不想侵入,可以自己在上层包装一下对应的命令。

然后在isearch-mode-map下的回车操作就是根据my/isearch--direction来搜索了。就 是如此简单。

(defun my/isearch-repeat (&optional arg)
  (interactive "P")
  (isearch-repeat my/isearch--direction arg))

当然在按Esc键的时候表明搜索已经结束了,此时应该重置当前的方式:

(define-advice isearch-exit (:after nil)
  (setq-local my/isearch--direction nil))

完整代码见下方:

(use-package isearch
  :ensure nil
  :bind (:map isearch-mode-map
         ([return] . my/isearch-repeat)
         ([escape] . isearch-exit))
  :config
  (defvar my/isearch--direction nil)
  (define-advice isearch-exit (:after nil)
    (setq-local my/isearch--direction nil))
  (define-advice isearch-repeat-forward (:after (_))
    (setq-local my/isearch--direction 'forward))
  (define-advice isearch-repeat-backward (:after (_))
    (setq-local my/isearch--direction 'backward))
  )

今天试了一下, 可以了, 是因为我的配置的问题,多谢

tempo不是builtin吧?没有啊

有的啊,记得 (require 'tempo)

插入SPDX形式的license头的功能已经独立出来了

Changelog

  • 2020-05-26 添加diredispell模式的介绍

dired

dired是一个用于directory浏览的mode,功能非常丰富。因此这里介绍的东西肯定不 能完全覆盖,会慢慢完善之。

在 dired 中用外部程序打开对应文件

dired-mode-map中,也是可以执行shell命令的。与之相关的命令有

  • dired-do-shell-command, 默认绑定在!
  • dired-smart-shell-command,默认绑定在M-!
  • async-shell-command,默认绑定在M-&

其中,通过配置dired-guess-shell-alist-user可以令dired-do-shell-command有一个 比较好的默认命令。例如,我这是样配置的:

(setq dired-guess-shell-alist-user `((,(rx "."
                                           (or
                                            ;; Videos
                                            "mp4" "avi" "mkv" "flv" "ogv" "mov"
                                            ;; Music
                                            "wav" "mp3" "flac"
                                            ;; Images
                                            "jpg" "jpeg" "png" "gif" "xpm" "svg" "bmp"
                                            ;; Docs
                                            "pdf" "md" "djvu" "ps" "eps")
                                           string-end)
                                      ,(cond ((eq system-type 'gnu/linux) "xdg-open")
                                             ((eq system-type 'darwin) "open")
                                             ((eq system-type 'windows-nt) "start")
                                             (t "")))))

这里考虑了多个平台下的差异。如linux平台下会使用xdg-open来打开对应的这些文件 (通过mimeinfo来配置,见~/.config/mimeapps.list)。但是它有一个缺点,会阻塞当 前的Emacs进程,所以仅适用于临时查看的需求。

dired-smart-shell-commanddired-do-shell-command类似,也会阻塞当前Emacs进 程。

async-shell-command则不会阻塞当前Emacs,唯一的缺点可能是会多弹出个buffer吧。 如果对async-shell-command的结果不是很感兴趣,可能通过shackle等类似的工具把忽 略对应的buffer

如果使用的是Emacs 28的话,并且已经设置了

(setq browse-url-handlers '(("\\`file:" . browse-url-default-browser)))

可以直接在dired里按W (browse-url-of-dired-file), 这会直接用外部程序打开。 当然,它不会阻塞Emacs

隐藏、显示 以.开头的文件

dired显示文件时使用的ls命令参数是由dired-listing-switches来控制的,它的默 认值是-al。如果不想要显示以.开头的文件,那么通过C-u s (sdired-sort-toggle-or-edit)来重新设置dired-listing-switches

如果只是想简单地隐藏当前目录下以.开头的文件,那么可以通过将满足^\\\.正则的行 删除就行(真实文件并没有删除,只是删除它的显示)。注意到dired-do-print命令基本 不怎么使用,于是可以利用advice来覆盖它,实现我们自己的dotfiles-toggle

;; 修改自 https://www.emacswiki.org/emacs/DiredOmitMode
(define-advice dired-do-print (:override (&optional _))
    "Show/hide dotfiles."
    (interactive)
    (if (or (not (boundp 'dired-dotfiles-show-p)) dired-dotfiles-show-p)
        (progn
          (setq-local dired-dotfiles-show-p nil)
          (dired-mark-files-regexp "^\\.")
          (dired-do-kill-lines))
      (revert-buffer)
      (setq-local dired-dotfiles-show-p t)))

这样只要按一下P就可以达到隐藏、显示的切换了。

如果不想自己写elisp,这里也有一个现成的包 https://github.com/mattiasb/dired-hide-dotfiles

ispell

ispell全称是interactive spell检查器,它支持ispell, aspellhunspell, 以下以hunspell为例。

;; 这里使用的是 en_US 字典,需要使用包管理安装对应的字典,类似的名字可能 hunspell-en_US
(setq ispell-dictionary "en_US"
      ispell-program-name "hunspell"
      ispell-personal-dictionary (expand-file-name "hunspell_dict.txt" user-emacs-directory))

这样就可以通过调用ispell-word来看一个单词是否正确了。如果是evil用户,这个函 数已经被绑定至z=上了。 \w/

Emacs自带的有dired-omit-mode。要require dired-x.el。改一下dired-omit-files的正则就可以自动隐藏dot file了。

通过dired-omit-mode的切换也是个不错的想法,我再添加一下

1赞

可以给 dired-listing-switches 添加 h 参数,文件大小的可读性要强一些