为 twidget 增加后端数据库

最近写了一个文本控件库 twidget,今天尝试用这个库在emacs中实现一个简单的笔记本应用,但我发现这种涉及大量文本数据且结构固定的应用更适合存储在数据库了。Twidget 虽然可以实现复杂多样的前端交互,但没有后端就像纸老虎。如果可以和数据库结合起来,便如虎添翼。于是便实现了 twidget-db 这个宏。

twidget-dbemacsqlemacsql-sqlite3 的使用做了一个封装。用户通过几行代码就可以建立数据库并进行增删改查操作。使用该宏后,可以得到两个函数 <prefix>-db<prefix>-db-action 。<prefix> 指 :prefix 属性的值。具体使用参考下面的例子。

(twidget-db "~/.emacs.d/notebook/notebook.db"
  :prefix "test"
  :tables '((notes [title content album timestamp])))

(test-db-action
 [:insert :into notes :values
          (["title" "content of note" "default" "2021-05-10"]
           ["title2" "content of note2" "default" "2021-05-11"])])
(test-db-action [:select [title content] :from notes])
;; => (("title" "content of note") ("title2" "content of note2"))

(test-db-action [:update notes :set [(= album "Emacs")
                                     (= content "new content of note")]
                         :where (= title "title2")])
(test-db-action [:select * :from notes])
;; => (("title" "content of note" "default" "2021-05-10") ("title2" "new content of note" "Emacs" "2021-05-11"))

(test-db-action [:delete :from notes])
(test-db-action [:select * :from notes])
;; => nil

上面的代码的意思是:在 ~/.emacs.d/notebook/notebook.db 文件中创建了数据库,并根据 :tables 参数的值生成了 notes 表。定义了 test-dbtest-db-action 两个函数。

test-db 返回一个数据库连接,可以被 emacsql 的函数使用,如 (emacsql (test-db) <sql>)test-db-action 做了进一步的封装,参数只有一个sql语句。Emacsql 的sql语句的写法请查看 emacsql文档

除了在 twidget 中使用,twidget-db 的代码也可以单独用于需要数据库的插件中。代码在 Github 自取。

发布于 2021-05-11

Twidget - Emacs中的文本控件库

Table of Contents

1 介绍

Twidget 的意思是 text widget, 即文本控件库。Emacs有一个 widget library,我使用后感觉不是很好用,并且需要使用鼠标操作。Twidget的设计遵循了简单易用,全键盘操作的理念,并且和 ewoc 做了很好的融合,可以轻松创建一个复杂的交互界面。

2 使用

2.1 基本控件

Twidget 目前有3个基本控件: twidget-text, twidget-choicetwidget-button 。我们可以使用一些统一的函数或宏来创建这些控件并插入到buffer中。每一种控件都有自己的键值对属性,可以通过指定键值对参数来控制其显示效果或行为。

twidget-text 文本控件

文本控件 有一个可编辑的区域,可以通过按键修改其展示的值并绑定到变量中。

主要键值对参数:

属性 类型 必须 含义
:bind 符号 控件的:value值会绑定到该符号变量中
:value 字符串 编辑区域的显示的值
:format 字符串 完整的控件字符串, 其中用 "[t]" 代表编辑区域
:action 函数/函数列表 编辑区域内容更新后触发的行为, 默认参数为当前的value
:plain 布尔值 隐藏编辑区域,显示为不可编辑的纯文本(和 twidget-insert 插入的文本不同的是,该区域可以通过代码修改)
:local 布尔值 是否为局部控件, 局部控件在每次刷新(twidget-page-refresh)时会恢复到原始值

twidget-choice 选择控件

选择控件 可以通过按键单选或多选一个列表中的值,并绑定到变量中。

主要键值对参数:

属性 类型 必须 含义
:bind 符号 控件的:value值会绑定到该符号变量中
:choices 列表 选项列表
:value 字符串/列表 选中的选项值,单选时是字符串,多选时是列表
:format 字符串 完整的控件字符串, 其中用 "[t]" 代表编辑区域
:action 函数/函数列表 选择选项后触发的行为, 默认参数为当前的value
:separator 字符串 不同选项间的分隔符,默认为一个空格
:multiple 布尔值 是否为多选, 默认为为单选
:fold 布尔值 是否折叠隐藏未选择的选项,默认全部显示
:require 布尔值 是否至少选中一项,默认非必须
:local 布尔值 是否为局部控件, 局部控件在每次刷新(twidget-page-refresh)时会恢复到原始值

twidget-button 按钮控件

按钮控件 点击后触发特定的行为。

属性 类型 必须 含义
:bind 符号 控件的:value值会绑定到该符号变量中
:value 字符串 按钮显示的文本
:action 函数 按钮作用后触发的行为
:help-echo 字符串 鼠标悬浮在按钮上时的tip,默认无
:follow-link 布尔值 按钮是否可用鼠标点击,默认不可

2.2 控件交互

使用 <tab> 激活下一个控件, <shift-tab> 激活上一个控件。被激活的控件会有数字提示,按数字键进行选择或文本更新,并触发相应的action函数。

2.3 控件API

环境准备

  • with-twidget-buffer (buffer-or-name &rest body)

    在指定 BUFFER-OR-NAME buffer 中创建控件。使用 pop-to-buffer 弹出buffer。

  • with-twidget-setup (&rest body)

    如果需要自己控制 buffer 的行为,使用该宏包裹twidget的代码。

  • twidget-buffer-setup & twidget-bind-keymap

    如果不使用 with-twidget-setup 宏,需要在 twidget 代码的开头和结尾分别调用上面的两个函数。

插入控件

  • twidget-create (twidget &rest args)

    twidget 是控件symbol,其余参数为键值对。

  • twidget-insert (&rest args)

    插入一段纯文本,和 insert 用法相同。

(defvar habit-regular-feq-type '("after-completion" "daily" "weekly" "monthly" "yearly"))
(defun habit-freq-type-switch (value)
  (message "current type is \"%s\"!" value))

(with-twidget-buffer "*Twidget Test*"
  (twidget-create 'twidget-text
    :bind 'habit-freq-title
    :value "Habit Frequency Selection"
    :format "Title: [t]"
    :action (lambda (value)
              (message "the title is \"%s\"" value)))
  (twidget-insert "\n\n")
  (twidget-create 'twidget-choice
    :bind 'habit-freq-type
    :choices habit-regular-feq-type
    :value "after-completion"
    :format "Repeat [t]"
    :action #'habit-freq-type-switch
    :separator "/"
    ;; :multiple nil
    ;; :fold nil
    ;; :local nil
    :require t))
example1.gif

查询控件属性值

  • twidget-query (bind-or-id property)

更新控件

一般用于 action 函数中

  • twidget-update (bind-or-id &rest properties)

    更新单个控件。bind-or-id 指被更新的控件的 :bind 属性值或 twidget-id(仅开发用)。properties 是一系列需要更新的键值对。

(defvar example-editors '("emacs" "vim" "vscode" "sublime text"))
(with-twidget-buffer "*Twidget Test*"
  (twidget-create 'twidget-choice
    :bind 'example-editor
    :choices example-editors
    :format "Editors: [t]"
    :value "emacs"
    :separator "/"
    :action (lambda (value)
              (twidget-update
               'example-string :value (capitalize value)))
    :require t)
  (twidget-insert "\n\n")
  (twidget-create 'twidget-text
    :bind 'example-string
    :format "  - [t] is my favorite editor!"
    :value "Emacs"
    :plain t)
  (twidget-create 'twidget-button
    :value "switch"
    :action (lambda (btn)
              (let* ((choices example-editors)
                     (editor (downcase example-editor))
                     (nth (seq-position choices editor)))
                (twidget-update
                 'example-editor
                 :value (capitalize (nth (% (1+ nth) (length choices)) choices)))))))
example2.gif
  • twidget-multi-update (&rest twidget-properties)

    更新多个控件。twidget-properties 的形式参考例子。

(defvar example-editors '("emacs" "vim" "vscode" "sublime text"))
(defvar example-websites
  '(("emacs" "https://www.gnu.org/software/emacs/")
    ("vim" "https://www.vim.org")
    ("vscode" "https://code.visualstudio.com")
    ("sublime text" "https://www.sublimetext.com")))

(with-twidget-buffer "*Twidget Test*"
  (twidget-create 'twidget-choice
    :bind 'example-editor
    :choices example-editors
    :format "\nEditors: [t]"
    :value "emacs"
    :separator "/"
    :action (lambda (value)
              (twidget-multi-update
               'example-string `(:value ,(capitalize value))
               'example-link `(:value ,(assoc value example-websites))))
    :require t)
  (twidget-create 'twidget-button
    :value "#switch#"
    :action (lambda (btn)
              (let* ((choices example-editors)
                     (editor (downcase example-editor))
                     (nth (seq-position choices editor)))
                (twidget-update
                 'example-editor
                 :value (nth (% (1+ nth) (length choices)) choices)))))
  (twidget-insert "\n\n")
  (twidget-create 'twidget-text
    :bind 'example-string
    :format "  - [t] is my favorite editor."
    :value "Emacs"
    :plain t)
  (twidget-create 'twidget-text
    :bind 'example-link
    :format "\n  - The website of [t0] is [t1]."
    :value '("emacs" "https://www.gnu.org/software/emacs/")
    :plain t))
example3.gif

删除控件

一般用于 action 函数中

  • twidget-delete (&rest binds-or-ids)

    bind 指控件绑定的变量,id 指 overlay twidget-id 的值。

(with-twidget-buffer "*Twidget Test*"
  (twidget-create 'twidget-choice
    :bind 'example-num
    :choices '("1" "2" "3" "4")
    :format "\nDelete the number [t] item in list."
    :value '("1") :separator "/"
    :multiple t)
  (twidget-create 'twidget-button
    :value "Delete"
    :follow-link t
    :action (lambda (btn)
              (let* ((binds (mapcar (lambda (num)
                                      (intern (format "example-str%s" num)))
                                    example-num))
                     (choices (twidget-query 'example-num :choices))
                     (new-choices
                      (seq-remove (lambda (num) (member num example-num)) choices)))
                (apply #'twidget-delete binds)
                (twidget-update 'example-num
                                ;; if update :choice, :value should also be updated.
                                :value (car new-choices)
                                :choices new-choices))))
  (twidget-insert "\n")
  (twidget-create 'twidget-text
    :bind 'example-str1
    :value "\n  1. this is the 1st item."
    :plain t)
  (twidget-create 'twidget-text
    :bind 'example-str2
    :value "\n  2. this is the 2nd item."
    :plain t)
  (twidget-create 'twidget-text
    :bind 'example-str3
    :value "\n  3. this is the 3rd item."
    :plain t)
  (twidget-create 'twidget-text
    :bind 'example-str4
    :value "\n  4. this is the 4th item."
    :plain t))
example4.gif

2.4 控件组API

控件组是由多个控件组合而成的。定义控件组可以复用相同的结构的控件,只更新需要更新的控件组,这对实现复杂的交互很有帮助。

  • twidget-group (&rest body) 定义控件组
  • twidget-group-create (group &optional next-group) 创建控件组
  • twidget-group-delete (group) 删除控件组
  • twidget-page-create (&rest groups) 创建所有控件组
  • twidget-page-refresh (&rest groups) 更新所有控件组
(twidget-group 'example-header
  (twidget-create 'twidget-choice
    :bind 'example-tab
    :choices '("主页" "关于" "记录" "更多")
    :format (concat (propertize "戈楷旎" 'face '(bold :height 1.2)) "   [t]")
    :value "主页" :separator "  "
    :action 'example-switch-tabs
    :require t))

(defun example-switch-tabs (value)
  (pcase value
    ("主页" (twidget-page-refresh 'example-header
                                  'example-index 'example-footer))
    ("关于" (twidget-page-refresh 'example-header
                                  'example-about 'example-footer))
    ("记录" (twidget-page-refresh 'example-header
                                  'example-diary 'example-footer))
    ("更多" (twidget-page-refresh 'example-header
                                  'example-more 'example-footer))))

(twidget-group 'example-index
  (twidget-insert "\n\n")
  (twidget-insert "This is the content of index page."))

(twidget-group 'example-about
  (twidget-insert "\n\n")
  (twidget-insert "This is the content of about page."))

(twidget-group 'example-diary
  (twidget-insert "\n\n")
  (twidget-insert "This is the content of diary page."))

(twidget-group 'example-more
  (twidget-create 'twidget-choice
    :bind 'example-more-tabs
    :choices '("留言" "视频")
    :format "> [t]"
    :action 'example-switch-more-tabs
    :require t))

(defun example-switch-more-tabs (value)
  (pcase value
    ("留言" (twidget-page-refresh 'example-header 'example-more
                                  'example-message 'example-footer))
    ("视频" (twidget-page-refresh 'example-header 'example-more
                                  'example-video 'example-footer))))

(twidget-group 'example-message
  (twidget-insert "\n\n")
  (twidget-insert "This is the content of message page."))

(twidget-group 'example-video
  (twidget-insert "\n\n")
  (twidget-insert "This is the content of video page."))

(twidget-group 'example-footer
  (twidget-insert "\n\n2019-" (format-time-string "%Y")
                  " 戈楷旎 | Licensed under CC BY-NC-SA | Powered by Django"))

(with-twidget-buffer "*twidget test*"
  (twidget-page-create 'example-header 'example-index 'example-footer))
example5.gif

3 说明

Twidget 目前处于测试开发中,后续 API 可能会有变动。如果使用,请密切关注更新。

发布于 2021-05-10

Emacs Lisp - ewoc使用介绍

1 介绍

Ewoc 是 Emacs's Widget for Object Collections的简写,它可以根据lisp对象的结构绘制和更新buffer的文本,就像MVC设计模式中的视图层。其中,生成的buffer的文本分为三个部分:特定的头部文本,lisp对象代表的数据元素的文本,特定的底部文本。一个ewoc对象的信息包含在以下内容中:

  • 用于产生文本的buffer
  • buffer中文本的起始位置
  • 头部和底部文本的内容
  • 一个双向链接的结点链,每一个结点包含:
    • 一个数据元素(单个lisp对象)
    • 结点链中上一个和下一个结点的链接
  • 一个将数据元素的文本插入到buffer中的打印函数

使用 ewoc-create 来定义一个ewoc对象,然后用其他的ewoc函数构建结点的结构并在buffer中显示。一旦在buffer中显示了文本,便可使用其他函数负责buffer的光标位置和结点间的数据通信。

让结点包含数据元素就像给变量设置值。通常结点包含数据元素的行为发生在将结点加入到ewoc对象过程中。可以通过下面两个函数获取和设置数据元素的值:

(ewoc-data node)
⇒ value

(ewoc-set-data node new-value)
⇒ new-value

也可以使用lisp对象(list or vector)或其他结构的索引作为数据元素的值。

当数据改变时,buffer中文本会相应的更新。使用 ewoc-update 函数更新所有结点,或者用 ewoc-invalidate ,使用 ewoc-map 函数给所有的结点增加条件判断。同时,可以使用 ewoc-deleteewoc-filter 函数删除无效的结点并设置新的结点。删除一个结点的同时会删掉该结点在buffer中展示的文本。

2 函数

这一部分的术语中, ewocnode 分别代表前面提到的两种结构,而 data 代表作为数据元素的lisp对象。三者的关系是:ewoc包含node,node包含data。下面详细介绍ewoc的每个函数。

ewoc-create pretty-printer &optional header footer nosep

创建并返回一个不包含结点的ewoc对象。 pretty-printer 是包含一个数据元素参数的函数,该数据元素的文本值将被插入到buffer中。(使用 insert 函数插入,不要用 insert-before-markers ,因为它对ewoc包的内部机制有干扰)

通常,该函数会自动在头部文本,底部文本和每一个node的文本后插入新的一行。设置 nosep 参数为 non-nil 可以不插入新行,这在需要将整个ewoc的文本显示在一行时很有用。ewoc创建时会在当前buffer下维护文本,所以在调用创建函数前要先切换到目标buffer。

ewoc-buffer ewoc

返回维护ewoc文本的buffer。

ewoc-get-hf ewoc

返回一个由头部文本和底部文本构成的 cons cell (header . footer)。

ewoc-enter-first ewoc data
ewoc-enter-last ewoc data

分别在ewoc的结点链的开头和结尾添加包含数据元素的新结点。

ewoc-enter-before ewoc node data
ewoc-enter-after ewoc node data

分别在指定结点的前面和后面添加一个包含数据元素的新结点。

ewoc-prev ewoc node
ewoc-next ewoc node

分别返回指定结点的前一个和后一个结点。

ewoc-nth ewoc n

返回以0开始的索引n的结点,n为负值时从最后开始索引。n超出范围时返回nil。

ewoc-data node

获取并返回node包含的数据元素。

ewoc-set-data node

设置node包含的数据元素值为data。

ewoc-locate ewoc &optional pos guess

确定并返回包含光标位置的结点。如果ewoc没有结点,返回nil。如果 pos 在第一个结点之前,返回第一个结点;在最后一个结点之后,返回最后一个结点。可选参数 guess 是有可能在pos附近的结点,它不会改变函数的结果但会让函数执行的更快。

ewoc-location node

返回结点的起始位置。

ewoc-goto-prev ewoc arg
ewoc-goto-next ewoc arg

分别移动光标到上arg个或下arg个结点。如果已经在第一个结点或ewoc为空, ewoc-goto-prev 不移动光标并返回nil,同样 ewoc-goto-next 不超过最后一个结点。除了以上特殊情况,函数返回移动到的结点。

ewoc-goto-node ewoc node

移动光标到结点的起始位置。

ewoc-refresh ewoc

重新生成ewoc的文本。执行过程是:删除header和footer之间的文本,然后为每一个结点依次调用 pretty-printer 函数生成文本。

ewoc-invalidate ewoc &rest nodes

ewoc-refresh 类似,但只更新 nodes 列表内的结点而不是所有结点。

ewoc-delete ewoc &rest nodes

删除所有 nodes 列表内的结点。

ewoc-filter ewoc predicate &rest args

为ewoc中的每一个数据元素调用 predicate 函数,删除断言为nil的结点。 args 是传递给断言函数的参数。

ewoc-collect ewoc predicate &rest args

为ewoc中的每一个数据元素调用 predicate 函数,返回断言为non-nil的元素列表。元素在列表中的顺序和buffer中一致。 args 是传递给断言函数的参数。

ewoc-map map-function ewoc &rest args

为ewoc中的每一个数据元素调用 map-function , 更新map函数返回为non-nil的结点。 args 是传递给map函数的参数。

3 例子

下面是使用ewoc实现的显示颜色组成的例子,颜色组成由buffer中的三个整数组成的向量表示。

(setq colorcomp-ewoc nil
      colorcomp-data nil
      colorcomp-mode-map nil
      colorcomp-labels ["Red" "Green" "Blue"])

(defun colorcomp-pp (data)
  (if data
      (let ((comp (aref colorcomp-data data)))
        (insert (aref colorcomp-labels data) "\t: #x"
                (format "%02X" comp) " "
                (make-string (ash comp -2) ?#) "\n"))
    (let ((cstr (format "#%02X%02X%02X"
                        (aref colorcomp-data 0)
                        (aref colorcomp-data 1)
                        (aref colorcomp-data 2)))
          (samp " (sample text) "))
      (insert "Color\t: "
              (propertize samp 'face
                          `(foreground-color . ,cstr))
              (propertize samp 'face
                          `(background-color . ,cstr))
              "\n"))))

(defun colorcomp (color)
  "Allow fiddling with COLOR in a new buffer.
     The buffer is in Color Components mode."
  (interactive "sColor (name or #RGB or #RRGGBB): ")
  (when (string= "" color)
    (setq color "green"))
  (unless (color-values color)
    (error "No such color: %S" color))
  (switch-to-buffer
   (generate-new-buffer (format "originally: %s" color)))
  (kill-all-local-variables)
  (setq major-mode 'colorcomp-mode
        mode-name "Color Components")
  (use-local-map colorcomp-mode-map)
  (erase-buffer)
  (buffer-disable-undo)
  (let ((data (apply 'vector (mapcar (lambda (n) (ash n -8))
                                     (color-values color))))
        (ewoc (ewoc-create 'colorcomp-pp
                           "\nColor Components\n\n"
                           (substitute-command-keys
                            "\n\\{colorcomp-mode-map}"))))
    (set (make-local-variable 'colorcomp-data) data)
    (set (make-local-variable 'colorcomp-ewoc) ewoc)
    (ewoc-enter-last ewoc 0)
    (ewoc-enter-last ewoc 1)
    (ewoc-enter-last ewoc 2)
    (ewoc-enter-last ewoc nil)))

通过定义改变 colorcomp-data 的值,完成选择过程和按键绑定,这个例子可以拓展成一个颜色选择的组件(MVC模式中的模型)。

(defun colorcomp-mod (index limit delta)
  (let ((cur (aref colorcomp-data index)))
    (unless (= limit cur)
      (aset colorcomp-data index (+ cur delta)))
    (ewoc-invalidate
     colorcomp-ewoc
     (ewoc-nth colorcomp-ewoc index)
     (ewoc-nth colorcomp-ewoc -1))))

(defun colorcomp-R-more () (interactive) (colorcomp-mod 0 255 1))
(defun colorcomp-G-more () (interactive) (colorcomp-mod 1 255 1))
(defun colorcomp-B-more () (interactive) (colorcomp-mod 2 255 1))
(defun colorcomp-R-less () (interactive) (colorcomp-mod 0 0 -1))
(defun colorcomp-G-less () (interactive) (colorcomp-mod 1 0 -1))
(defun colorcomp-B-less () (interactive) (colorcomp-mod 2 0 -1))

(defun colorcomp-copy-as-kill-and-exit ()
  "Copy the color components into the kill ring and kill the buffer.
     The string is formatted #RRGGBB (hash followed by six hex digits)."
  (interactive)
  (kill-new (format "#%02X%02X%02X"
                    (aref colorcomp-data 0)
                    (aref colorcomp-data 1)
                    (aref colorcomp-data 2)))
  (kill-buffer nil))

(setq colorcomp-mode-map
      (let ((m (make-sparse-keymap)))
        (suppress-keymap m)
        (define-key m "i" 'colorcomp-R-less)
        (define-key m "o" 'colorcomp-R-more)
        (define-key m "k" 'colorcomp-G-less)
        (define-key m "l" 'colorcomp-G-more)
        (define-key m "," 'colorcomp-B-less)
        (define-key m "." 'colorcomp-B-more)
        (define-key m " " 'colorcomp-copy-as-kill-and-exit)
        m))
发布于 2020-08-27

Emacs Hack - 通过列表数据创建表格

1 问题描述

OrgMode内置的创建表格的函数是 org-table-create , 传入"列数x行数"参数即可生成特定行数、列数的表格。这种交互函数在编写org文档时很实用,但在代码中却显得鸡肋。因为在代码中,我们通常希望表格和数据可以一起生成,而不是手动添加数据。

我在折腾 gk-habit.el 时,就产生了这样的需求:生成习惯的月度打卡视图。就像下面这个样子:

Sun Mon Tue Wed Thu Fri Sat
            1
            --
2 3 4 5 6 7 8
-- -- -- -- -- --
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
         

2 思路分析

解决这个问题的关键是把握几个操作org表格的函数 org-create-table, org-table-next-field, org-table-insert-hline, org-table-kill-row… 然后就是在表格创建的过程中依次插入数据。用于创建表格的每一行的数据用列表表示,分隔线用 hl 对象表示。

  • 首先创建一个一行n列的表格,因为 org table 的函数只有在表格内才能使用。其中n为每行元素的个数。
  • 在数据列表中循环,如果元素是一个list,表示是数据。继续在该list中循环,插入数据后跳到下一个单元格(注意数字要转为字符串)。
  • 如果元素是 hl 对象,表示是分隔线,直接插入一行分割线 (org-table-insert-hline 1)
  • 每一行插入最后一个数据后会执行“跳到下一个单元格”的操作,当右边没有单元格时会自动插入新的一行。
  • 因此,最后会多出一行,用 org-table-kill-row 函数删掉。

3 代码实现

(defun gk-org-table-create (LIST)
  "Create org table from a LIST form at point."
  (let ((column (catch 'break
                  (dolist (row-data LIST)
                    (when (listp row-data)
                      (throw 'break (length row-data))))))
        (beg (point)))
    (org-table-create (concat (number-to-string column) "x1"))
    (goto-char beg)
    (when (org-at-table-p)
      (org-table-next-field)
      (dotimes (i (length LIST))
        (let ((row-data (nth i LIST)))
          (if (listp row-data)
              (dolist (data row-data)
                (cond
                 ((numberp data)
                  (insert (number-to-string data)))
                 ((null data)
                  (insert ""))
                 (t (insert data)))
                (org-table-next-field))
            (when (equal 'hl row-data)
              (org-table-insert-hline 1)))
          (when (= i (1- (length LIST)))
            (org-table-kill-row))))))
  (forward-line))

4 使用案例

(gk-org-table-create
 '(("n1" "n2" "n3" "n4" "n5")
   hl
   (1 2 3 4 5)
   (6 7 8 9 10)
   hl
   ("c1" "c2" "c3" "c4" "c5")
   hl
   ("a" "b" "c" "d" "e")
   ("f" "g" "h" "i" "j")))
| n1 | n2 | n3 | n4 | n5 |
|----+----+----+----+----|
| 1  | 2  | 3  | 4  | 5  |
| 6  | 7  | 8  | 9  | 10 |
|----+----+----+----+----|
| c1 | c2 | c3 | c4 | c5 |
|----+----+----+----+----|
| a  | b  | c  | d  | e  |
| f  | g  | h  | i  | j  |

实现开篇提出的习惯打卡的视图是个更复杂的问题,这里涉及到了不同月份的天数不同,起始星期不同,以及每天对应的打卡状态不同等问题。解决了这些问题后,将得到的数据整合成 gk-org-table-create 合法的数据列表形式即可生成相应的表格。相关代码在这里

如果你有更简单、漂亮的实现,欢迎留言探讨~

发布于 2020-08-19

Emacs Hack - 习惯记录与打卡的demo

想要培养一些好的习惯,想要不那么费力的变得更自律。

读了一些关于时间管理和习惯培养的书籍和文章,我意识到“仪式感”和“记录反思”的重要性。事实上,以自己喜欢的方式做记录是件很开心的事情,我已经连续80天写晨间日记和晚间总结。现在, 写日志已经变成了习惯。但我希望培养更多好的习惯,用我自己喜欢的方式(在emacs中),所以有了gk-habit这个demo。

2 思路

gk-habit使用emacsql-sqlite创建数据库,有两个表 "habit" 和 "record"。前者记录习惯及其相关参数,后者记录习惯打卡的数据。

habit表

  • create-time 习惯创建的时间
  • name 习惯名称
  • frequency-type 习惯打卡频率类型,包括“每天”、“每周几重复”,“一周几次”,“一月几次”
  • frequency-param 频率类型的参数,每天重复则为nil、每周几重复则用数字表示(134表示每周一三四)、一周或一月的打卡的次数。
  • period 习惯发生的时间时间段
  • remind-time 提醒打卡的时间
  • remind-string 提醒打卡时的文本
  • status 习惯的状态,active或archive,archive表示不再跟踪该习惯
  • archive-time 归档的时间

record表

  • create-time 记录创建的时间
  • habit 记录的习惯
  • status 习惯完成状态(DONE完成,MISS未完成)
  • comment 未完成的原因

3 代码

;;; habit record - gk-habit
(require 'cl-lib)
(require 'emacsql-sqlite)

(defconst gkh-buffer "*Gk-Habit*")

(defvar gkh-db (emacsql-sqlite3 "~/.emacs.d/gk-habit/habit.db"))

(defvar gkh-file "~/.emacs.d/gk-habit/habit.org")

(defvar gkh-record-status '("DONE" "MISS"))

(defvar gkh-frequency-type '("everyday" "repeat" "by-week" "by-month"))

(defvar gkh-period '("get-up" "morning" "noon" "afternoon" "evening" "before-sleep"))

(defvar gkh-table-habit-column
  '("create-time" "name" "frequency" "frequency-param"
    "peroid" "remind-time" "remind-string" "status" "archive-time"))

(defvar gkh-table-record-column
  '("create-time" "habit" "status" "comment"))

(defun gkh-db-create-tables ()
  (interactive)
  "Create database tables of gk-habit."
  (emacsql gkh-db [:create-table habit
                                 ([(create-time string :primary-key :not-null)
                                   (name string :not-null :unique)
                                   (frequency-type string :not-null)
                                   (frequency-param)
                                   (period string :not-null)
                                   (remind-time)
                                   (remind-string)
                                   (status string :not-null)
                                   (archive-time string)])])

  (emacsql gkh-db [:create-table record
                                 ([(create-time string :primary-key :not-null)
                                   (habit string :not-null)
                                   (status string :not-null)
                                   (comment string)]
                                  (:foreign-key [habit] :references habit [name]
                                                :on-delete :cascade))]))

(defun gkh-db-drop-tables ()
  "Drop database tables of gk-habit."
  (interactive)
  (let* ((tables (emacsql gkh-db [:select name
                                          :from sqlite_master
                                          :where (= type 'table)]))
         (table (completing-read "Choose a table: " tables nil t)))
    (emacsql gkh-db `[:drop-table ,(intern table)])))

(defun gkh--frequency-params (frequency-type)
  "Get the habit frequency parameters"
  (let (param)
    (cond
     ((string= frequency-type "everyday")
      (setq param nil))
     ((string= frequency-type "repeat")
      (setq param (completing-read "repeated day (exam. \"135\" means habit repeat on Monday, Wensday and Friday in every week.): " nil)))
     ((string= frequency-type "by-week")
      (setq param (completing-read "how many times a week: " nil)))
     ((string= frequency-type "by-month")
      (setq param (completing-read "how many times a month: " nil))))
    param))

(defun gkh-init ()
  "Gk-habit initialize, create database and org tables."
  (interactive)
  (ignore-errors (gkh-db-create-tables))
  (gkh-org-table-draw))

(defun gkh-new ()
  "Add a new habit"
  (interactive)
  (cl-block 'return
    (let* ((create-time (format-time-string "%Y-%m-%d %T"))
           (habit
            (let ((temp (completing-read "name of habit: " nil))
                  (habits (mapcar 'car (emacsql gkh-db [:select name :from habit
                                                                :where (= status "Active")]))))
              (if (member temp habits)
                  (cl-return-from 'return
                    (message "the habit '%s' already exist!" temp))
                temp)))
           (frequency-type (completing-read "frequency of habit: " gkh-frequency-type nil t))
           (frequency-param (gkh--frequency-params frequency-type))
           (period  (completing-read "period of habit: " gkh-period nil t))
           (remind-time
            (let ((temp (completing-read "remind this habit at: " nil)))
              (if (string= "" temp)
                  nil temp)))
           (remind-string
            (let ((temp (completing-read "habit remind sentence: " nil)))
              (if (string= "" temp)
                  nil temp))))
      (emacsql gkh-db `[:insert :into habit
                                :values ([,create-time ,habit ,frequency-type ,frequency-param ,period ,remind-time ,remind-string "Active" nil])])
      (gkh-org-table-draw)
      (message "Habit '%s' is added!" habit))))

(defun gkh-record ()
  "Insert a habit redord in table."
  (interactive)
  (let* ((create-time (format-time-string "%Y-%m-%d %T"))
         (habit (completing-read "Choose a habit: "
                                 (emacsql gkh-db [:select [name] :from habit
                                                          :where (= status "Active")])))
         (status (completing-read "Is the habit done?" gkh-record-status nil t))
         (comment
          (when (string= "MISS" status)
            (completing-read "Reason why missed: " nil))))
    (emacsql gkh-db `[:insert-into record
                                   :values ([,create-time ,habit ,status ,comment])])
    (gkh-org-table-draw)
    (message "Habit '%s' is %s, record on %s, %s" habit status create-time comment)))

(defun gkh-archive ()
  "Archive a habit"
  (interactive)
  (let* ((habits (emacsql gkh-db [:select name :from habit
                                          :where (= status "Active")]))
         (habit (completing-read "Choose a habit: " habits nil t)))
    (emacsql gkh-db `[:update habit
                              :set [(= status "Archive") (= archive-time ,(format-time-string "%Y-%m-%d %T"))]
                              :where (= name ,habit)])
    (gkh-org-table-draw)
    (message "habit %s has been archived!" habit)))

(defun gkh-org-table-draw ()
  "Draw gk-habit database in org table."
  (interactive)
  (let* ((table-alist '(("habit" . gkh-table-habit-column)
                        ("record" . gkh-table-record-column))))
    (with-temp-file gkh-file
      (goto-char (point-min))
      (dotimes (i (length table-alist))
        (let* ((headline (car (nth i table-alist)))
               (column-list (eval (cdr (nth i table-alist))))
               (column-num (length column-list)))
          (insert (concat "* " headline " table\n"))
          (org-table-create (concat (format "%s" column-num) "x2"))
          (dotimes (j column-num)
            (org-table-next-field)
            (insert (nth j column-list)))
          (let ((items (emacsql gkh-db `[:select * :from ,(intern headline)])))
            (dotimes (m (length items))
              (dotimes (n column-num)
                (org-table-next-field)
                (insert (format "%s" (nth n (nth m items)))))))
          (org-table-align)
          (forward-line 2)
          (end-of-line)
          (newline 2))))))

(defun gkh-org-table-display ()
  "Display gk-habit org table in a bottom buffer."
  (interactive)
  (gkh-org-table-draw)
  (if (string= (buffer-name) gkh-buffer)
      (message "Already in the Gk Habit buffer."))
  (select-window
   (or (get-buffer-window gkh-buffer)
       (selected-window)))
  (with-current-buffer (get-buffer-create gkh-buffer)
    (org-mode)
    (read-only-mode -1)
    (erase-buffer)
    (insert (file-contents gkh-file))
    (valign-mode)
    (goto-char (point-min))
    (read-only-mode 1))
  (view-buffer gkh-buffer 'kill-buffer))

(provide 'gk-habit)

4 计划

  • 设置习惯打卡时间提醒
  • 使用matplotlib库绘制习惯打卡统计图
发布于 2020-08-04

Emacs Hack - 分割文件相似内容

实际问题

将一个文件中的相似块分割成若干个文件。比如下面这个例子:

第一回: 灵根育孕源流出 心性修持大道生
  这是第一段内容.....
  这是第二段内容.....
  这是第三段内容.....
  更多...

第二回: 悟彻菩提真妙理 断魔归本合元神
  这是第一段内容.....
  这是第二段内容.....
  这是第三段内容.....
  更多...

第三回: 四海千山皆拱伏 九幽十类尽除名
  这是第一段内容.....
  这是第二段内容.....
  这是第三段内容.....
  更多...

假设有一个长篇小说txt文件,每一回有相似的结构。要解决的问题是:如何将每一回分割成单独的文件?文件名是每一回的标题,文件的内容就是每一回的内容。

解决思路

解决这种实际的问题,我的思路一般是:用人的思维找到直观的解决办法。然后,将人的思维转换成对应的代码逻辑,比如:运用什么控制语句?使用什么数据结构?涉及什么特殊算法?等等。

首先我们从人的思维出发考虑如何解决这个问题。最直接的方法是:根据开头“第一回:”这几个字确定这一回的标题,然后确定接下来直到“第二回:”之间的部分为第一回的内容。然后将第一回的内容写到以标题命名的文件中。接下里,以此类推,直到最后没有内容。

接下来,思考如何将这个过程转化成代码的逻辑。

显然,这里是循环判断的逻辑,循环的范围是从第一行到最后一行。分析过程如下:

  1. 搜索第一行,是标题行,记录标题并移到下一行。
  2. 搜索这一行,不是标题行,记录行首的位置start。判断下一行是否是标题行,不是则移到下一行。
  3. 重复过程2,直到当前行的下一行是标题行时,记录本行行首的位置end。
  4. 将start和end之间的内容写到以标题命名的文件中。移到下一行。
  5. 重复上面的过程1,2,3,4。直到光标到达最后。

代码实现

根据上面的分析过程,有如下的代码:

(defun my-split-file (file)
  (interactive "fchoose a file: ")
  (let ((filedir "~/test-dir/")
        filename start end content)
    (with-current-buffer (get-buffer-create "*split-file*")
      (insert-file-contents file)
      (goto-char (point-min))
      (while (< (point) (point-max))
        (if (search-forward-regexp "^第.+回:" (line-end-position) t)
            (progn 
              (setq filename (string-trim (thing-at-point 'line))) 
              (next-line)
              (beginning-of-line)
              (setq start (point))) ;; 获取内容的起始位置start
          (save-excursion ;; 保存光标的位置:只判断下一行的情况,不改变实际要操作的光标。
            (next-line)
            (beginning-of-line)
            (when (or (search-forward-regexp "^第.+回:" (line-end-position) t)
                      (= (point) (point-max)))
              (previous-line)
              (end-of-line)
              (setq end (point)) ;; 获取内容的结尾位置end
              (setq content (buffer-substring-no-properties start end)) ;; 获取start和end之间的内容。
              (with-temp-file (concat filedir filename ".txt")
                (insert content)) ;; 写文件
              ))
          (next-line)
          (beginning-of-line))))))
发布于 2020-06-28

Emacs Hack - 批量替换字符串

实际问题

不使用interactive function(M-%)如何批量替换文件中的字符串?应用场景为文字校验。

解决思路

字符串替换分三种情况:

  1. 在当前buffer替换
  2. 在指定文件中替换
  3. 为指定目录下所有文件替换。

用于替换的pair有两种来源:

  1. repl-string-listrepl-regexp-list 变量
  2. repl-file 对应的文件。

字符串查找的方式有两种,分别对应两个函数:

  1. 纯字符串 (replall-string)
  2. 正则表达式 (replall-regexp)

优先使用列表变量的值,如果该变量值为nil则使用repl-file对应的文件中的值。其中文件中的pair只能用于 replall-string 函数。两个变量列表的值分别对应两个函数。repl-file的格式为每行一个pair,分别为被替换的字符串和用于替换的字符串,用空格隔开。

代码实现

主函数 replall-stringreplall-regexp 可以选择替换类型。对于具体的情况也可以直接使用具体的替换函数。代码如下:

(setq repl-string-list
      '(("old" "new")
        ("test" "测试")
        ("错误" "right")
        ("隔开你" "戈楷旎")))

(setq repl-regexp-list
      '(("\\." "。")))

(setq repl-file "~/replace.txt")

(defun replall--read-pair-from-file ()
  (let ((repl-list '()))
    (with-temp-buffer
      (insert-file-contents repl-file)
      (goto-char (point-min))
      (while (< (point) (point-max))
        (setq repl-pair (split-string (thing-at-point 'line) "[ \f\t\n\r\v]+" t "[ \f\t\n\r\v]+"))
        (if (null repl-pair)
            (next-line)
          (next-line)
          (setq repl-list (append repl-list (list repl-pair))))))
    repl-list))

(defun replall--get-repl-string-list ()
  (if (bound-and-true-p repl-string-list)
      repl-string-list
    (replall--read-pair-from-file)))

(defun replall--get-repl-regexp-list ()
  (if (bound-and-true-p repl-regexp-list)
      repl-regexp-list
    (message "please set variable 'repl-regexp-list'!")))

(defun replall--string (file lst)
  (with-temp-buffer
    (insert-file-contents file)
    (goto-char (point-min))
    (dolist (pair lst)
      (while (search-forward (car pair) nil t)
        (replace-match (cadr pair)))
      (goto-char (point-min)))
    (write-file file)))

(defun replall--regexp (file lst)
  (with-temp-buffer
    (insert-file-contents file)
    (goto-char (point-min))
    (dolist (pair lst)
      (while (re-search-forward (car pair) nil t)
        (replace-match (cadr pair)))
      (goto-char (point-min)))
    (write-file file)))

(defun replall-string-in-curr-buffer ()
  (interactive)
  (let ((curr-file (buffer-file-name (current-buffer)))
        (repl-list (replall--get-repl-string-list)))
    (replall--string curr-file repl-list)))

(defun replall-regexp-in-curr-buffer ()
  (interactive)
  (let ((curr-file (buffer-file-name (current-buffer)))
        (repl-list (replall--get-repl-regexp-list)))
    (replall--regexp curr-file repl-list)))

(defun replall-string-in-file (file repl)
  (interactive "fchoose a file to be processed: ")
  (let ((repl-list (replall--get-repl-string-list)))
    (replall--string file repl-list)))

(defun replall-regexp-in-file (file repl)
  (interactive "fchoose a file to be processed: ")
  (let ((repl-list (replall--get-repl-regexp-list)))
    (replall--regexp file repl-list)))

(defun replall--get-real-files-in-dir (dir)
  (let ((real-files)
        (files (directory-files dir)))
    (dolist (file files)
      (when (not (or (string= "." (substring file 0 1))
                     (string= "#" (substring file 0 1))
                     (string= "~" (substring file -1))))
        (push file real-files)))
    real-files))

(defun replall-string-in-directory (dir)
  (interactive "Dchoose a directory to be processed: ")
  (let* ((repl-list (replall--get-repl-string-list))
         (real-files (replall--get-real-files-in-dir dir)))
    (dolist (file real-files)
      (replall--string (concat dir file) repl-list))))

(defun replall-regexp-in-directory (dir)
  (interactive "Dchoose a directory to be processed: ")
  (let* ((repl-list (replall--get-repl-regexp-list))
         (real-files (replall--get-real-files-in-dir dir)))
    (dolist (file real-files)
      (replall--regexp (concat dir file) repl-list))))

(defun replall-string (type)
  (interactive "sreplace string: 1.in current buffer  2.in a file  3.in a directory (input 1~3): ")
  (cond
   ((string= type "1")
    (replall-string-in-curr-buffer))
   ((string= type "2")
    (call-interactively #'replall-string-in-file))
   ((string= type "3")
    (call-interactively #'replall-string-in-directory))
   (t (message "please input 1~3!"))))

(defun replall-regexp (type)
  (interactive "sreplace regexp: 1.in current buffer  2.in a file  3.in a directory (input 1~3): ")
  (cond
   ((string= type "1")
    (replall-regexp-in-curr-buffer))
   ((string= type "2")
    (call-interactively #'replall-regexp-in-file))
   ((string= type "3")
    (call-interactively #'replall-regexp-in-directory))
   (t (message "please input 1~3!"))))
发布于 2020-06-16

基于Emacs-Lisp的HTML模版库

English Document

1 介绍

pp-html 是使用elisp开发的html模版语言。它借鉴了部分 Liquid template language 的设计思路,包括对象,标签和过滤器三部分。通过书写elisp的S表达式,读者可以快速便捷地构建简单html片段或复杂的html页面。其中 :include:extend (包含和继承) 标签使得模块化构建html成为可能,提高了代码重用率。

2 安装

克隆此代码仓库:

$ git clone https://github.com/Kinneyzhang/pp-html.git <path-to-pp-html>

安装相关依赖: dash, sweb-mode

然后在emacs配置中添加如下两行:

(add-to-list 'load-path "<path-to-pp-html>")
(require 'pp-html)

3 使用

3.1 基础

pp-html使用elisp的S表达式来构建html代码,使用 pp-html 函数对S表达式求值。下面有一些简单的例子,便于理解基本的使用方法。

3.1.1 单个S表达式

单个S表达式的构成为: (html标签 多个属性键值对 内容)

其中html标签是必须的,其余可缺省。属性键值对的书写语法为Plist形式。特别地是,对CSS选择器设置了语法糖,用 "." 表示 "class","@" 表示 "id",其值为符号后面的内容。对于无值属性(例如 async)有两种写法:1. (:async nil) 2. 直接写:async,要求不能是最后一个属性。

(pp-html '(a "content"))
(pp-html '(a @id .class))
(pp-html '(a :id "id" :class "class"))
(pp-html '(a .test :href "url" :target "_blank" "content"))
(pp-html '(link :async :rel "stylesheet" :href "url" :async nil))
<a>content</a>
<a id="id" class="class"></a>
<a id="id" class="class"></a>
<a class="test" href="url" target="_blank">content</a>
<link async rel="stylesheet" href "url" async/>

3.1.2 并列S表达式

并列的多个S表达式直接用括号包含。

(pp-html
 '((div .div1 "div-content")
   (p "paragraph")
   (a :href "url" "a-content")
   (img :src "path")
   (ul .org-ul
       (li "1")
       (li "2")
       (li "3"))))
<div class="div1">div-content</div>
<p>paragraph</p>
<a href="url">a-content</a>
<img src="path"/>
<ul class="org-ul">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

3.1.3 嵌套S表达式

在同一html标签的S表达式内代表该元素的子元素,否则为兄弟元素。

(pp-html
 '(div .container
       (div .row
            (div .col-8
                 (p "paragraph 1"))
            (div .col-4
                 (p "paragraph 2")))))
<div class="container">
  <div class="row">
    <div class="col-8">
      <p>paragraph 1</p>
    </div>
    <div class="col-4">
      <p>paragraph 2</p>
    </div>
  </div>
</div>

3.2 对象

对象告诉pp-html在页面何处显示对象的值。它包括三类:变量求值、对象属性求值和函数求值。可以使用 pp-html-eval 函数获取对象的值。

3.2.1 变量求值

变量求值的基本语法是在变量前加上"$"符号。

(let ((var1 "happy hacking emacs"))
  (pp-html-eval '$var1))
happy hacking emacs

变量可应用于S表达式的任何部分。

(let ((url "https://geekinney.com/")
      (name "戈楷旎"))
  (pp-html '(a :href $url $name)))
<a href="https://geekinney.com/">戈楷旎</a>

3.2.2 对象属性求值

特别地,对于Plist对象使用"."来获取属性值。

(let ((site '(:name "戈楷旎" :domain "geekinney.com" :author "Geekinney")))
  (pp-html '(div .site-info
                 (p $site.name)
                 (p $site.domain)
                 (p $site.author))))
<div class="site-info">
  <p>戈楷旎</p>
  <p>geekinney.com</p>
  <p>Geekinney</p>
</div>

3.2.3 函数求值

函数求值的S表达式语法为 ($ <function> <args…>), 函数的参数也可写成变量形式。

(let ((var1 "happy")
      (var2 " hacking"))
  (pp-html-eval '($ concat $var1 $var2 " emacs")))
happy hacking emacs

函数可嵌套调用,或直接写,两种写法等价。

(let ((var1 "now")
      (var2 " is ")
      (now '(current-time)))
  (pp-html-eval '($ concat ($ upcase $var1) $var2 ($ format-time-string "%Y-%m-%d" $now)))
  (pp-html-eval '($ concat (upcase $var1) $var2 (format-time-string "%Y-%m-%d" $now))))
NOW is 2020-05-10
NOW is 2020-05-10

同理,函数也可用于S表达式的任何部分,这样pp-html就可以任意使用elisp丰富强大的函数库了。

3.3 标签

标签为模版创造了逻辑和流程控制,它用冒号表示并且放在S表达式的第一个位置: (:tag …)。标签分为5类:

  • 变量定义
  • 流程控制
  • 迭代
  • 代码块

3.3.1 变量定义

assign

定义变量,相当于elisp的let或setq。

(pp-html
 '((:assign str1 "happy"
            str2 "hacking"
            str3 "emacs")
   (p ($ concat $str1 " " $str2 " " $str3))))
<p>happy hacking emacs</p>

3.3.2 流程控制

if

如果条件为真执行第一个代码块,否则执行第二个

(pp-html
 '((:assign bool nil)
   (:if $bool (p "true")
        (p "false"))))
<p>false</p>

unless

和if相反,如果条件为假,执行第一个代码块,否则执行第二个。

(pp-html
 '((:assign bool nil)
   (:unless $bool (p "true")
        (p "false"))))
<p>true</p>

cond

执行每一个分支,直到条件满足,执行满足条件的代码块。

(pp-html
 '((:assign case "case3")
   (:cond
    ($ string= $case "case1") (p "case1 branch")
    ($ string= $case "case2") (p "case2 branch")
    ($ string= $case "case3") (p "case3 branch")
    t (p "default branch"))))
<p>case3 branch</p>

3.3.3 迭代

for

for循环

(pp-html
 '((:assign editors ("vim" "emacs" "vscode"))
   (ul
    (:for editor in $editors
          (li :id $editor $editor)))))
<ul>
  <li id="vim">vim</li>
  <li id="emacs">emacs</li>
  <li id="vscode">vscode</li>
</ul>

3.3.4 代码块

include

在一个代码块中包含另一个代码块。

(setq block1
      '(p "block1 content"
          (a :href "url" "content")))

(setq block2
      '(div .block2
            (p "block2 content")
            (:include $block1)))

(pp-html block2)
<div class="block2">
  <p>block2 content</p>
  <p>
    block1 content
    <a href="url">content</a>
  </p>
</div>

extendblock

代码块继承。如果新代码块重写了 :block 标签之间的内容,覆盖原代码块对应的部分,其余保持不变。

(setq base-block '(p .base
                     (:block block-name (span "base content")))
      extend-block1 '(:extend $base-block
                              (:block block-name))
      extend-block2 '(:extend $base-block
                              (:block block-name
                                      (span "extended content"))))
(pp-html
 '((div "extend the default"
        (:include $extend-block1))
   (div "extend with new"
        (:include $extend-block2))))
<div>
  extend the default
  <p class="base">
    <span>base content</span>
  </p>
</div>
<div>
  extend with new
  <p class="base">
    <span>extended content</span>
  </p>
</div>

3.4 过滤器

过滤器的语法形式为 (/ <value> <:filter args> …)。过滤器作用于<value>,可以有参数,也可以没有。

3.4.1 自定义过滤器

pp-html支持自定义过滤器,使用 pp-html-define-filter 函数,它有两个参数:过滤器名称和过滤函数。例:

(pp-html-define-filter :add 'pp-html-filter-add)
(defun pp-html-filter-add (value arg)
  "Add a value to a number"
  (let ((arg (if (stringp arg)
                 (string-to-number arg)
               arg)))
    (+ value arg)))

3.4.2 内置过滤器

abs: 取绝对值

(pp-html-eval '(/ -5 :abs)) ;; => 5

add: 加上一个数

(pp-html-eval '(/ 4 :add 5)) ;; => 9

append: 结合两个列表

(let ((list1 '(1 2 3))
      (list2 '(5 6 7)))
  (pp-html-eval '(/ $list1 :append $list2))) ;; => (1 2 3 5 6 7)

capitalize: 第一个单词首字母大写

(pp-html-eval '(/ "happy hacking emacs!" :capitalize)) ;; => Happy hacking emacs!

compact: 删除列表中所有的nil

(let ((lst '(nil 1 2 nil 3 4 nil)))
  (pp-html-eval '(/ $lst :compact))) ;; => (1 2 3 4)

concat: 字符串连接

(let ((str1 "happy hacking ")
      (str2 "emacs"))
  (pp-html-eval '(/ $str1 :concat $str2))) ;; => happy hacking emacs

default: 不是nil或空字符串,设为默认值

(let ((str1 "")
      (str2 "new value")
      (lst1 '(1 2 3))
      (lst2 nil))
  (pp-html-eval '(/ $str1 :default "default value")) ;; => default value
  (pp-html-eval '(/ $str2 :default "default value")) ;; => new value
  (pp-html-eval '(/ $lst1 :default (4 5 6))) ;; => (1 2 3)
  (pp-html-eval '(/ $lst2 :default (4 5 6))) ;; => (4 5 6)
  )

escape: html特殊字符转义

(pp-html-eval '(/ "Have you read 'James & the Giant Peach'?" :escape)) ;; => Have you read &apos;James &amp; the Giant Peach&apos;?

join: 使用分隔符连接列表中字符串

(let ((lst '("happy" "hacking" "emacs")))
  (pp-html-eval '(/ $lst :join "-"))) ;; => happy-hacking-emacs

More useful filters are on the way!

3.5 综合

综合以上语法的例子:

(setq assign-vars
      '(:assign name "geekinney blog"
                description "Emacs is a lifestyle :-) And happy hacking emacs!"
                menus ((:path "/" :name "Index")
                       (:path "/archive" :name "Archive")
                       (:path "/category" :name "Category")
                       (:path "/about" :name "About"))
                comment-p t
                comment-type "disqus"
                valine-block (p "this is valine block")
                disqus-block (p "this is disqus block")))
(setq header-block
      '(header @topheader
               (a @logo :href "/" $name)
               (p .description $description)))

(setq menu-block
      '(nav @topmenu
            (:for menu in $menus
                  (a :href $menu.path $menu.name))))

(setq article-block
      '(article
        (p ($ concat "Function: the site name is " ($ upcase $name)))
        (p (/ "Filter: the site name is " :concat (/ $name :capitalize)))
        (p (/ ("happy" "hacking" "emacs") :join " " :capitalize :concat "!"))))

(setq comment-block
      '(div @comment
            (:if comment-p
                 (:cond
                  ($ string= $comment-type "valine") (:include $valine-block)
                  ($ string= $comment-type "disqus") (:include $disqus-block)
                  t nil)
                 (p "The comment is closed!"))))

(setq side-block
      '(aside @sidebar
              (:block side-block
                      (p "this is base sidebar"))))

(setq footer-block
      '(:block footer-block
               (footer
                (p "this is base footer."))))

(setq base-block
      '((:include $assign-vars)
        (body
         (div .container
              (div .row
                   (div .col-12
                        (:include $header-block)))
              (div .row
                   (div .col-12
                        (:include $menu-block)))
              (div .row
                   (div .col-12 .col-sm-12 .col-md-8 .col-lg-8
                        (:include $article-block)
                        (:include $comment-block))
                   (div .col-md-4 .col-lg-4
                        (:include $side-block)))
              (div .row
                   (div .col-12
                        (:include $footer-block)))))))

(pp-html
 '(:extend $base-block
           (:block side-block
                   (p "this is extended sidebar"))
           (:block footer-block)))
<body>
  <div class="container">
    <div class="row">
      <div class="col-12">
        <header id="topheader">
          <a id="logo" href="/">geekinney blog</a>
          <p class="description">Emacs is a lifestyle :-) And happy hacking emacs!</p>
        </header>
      </div>
    </div>
    <div class="row">
      <div class="col-12">
        <nav id="topmenu">
          <a href="/">Index</a>
          <a href="/archive">Archive</a>
          <a href="/category">Category</a>
          <a href="/about">About</a>
        </nav>
      </div>
    </div>
    <div class="row">
      <div class="col-12 col-sm-12 col-md-8 col-lg-8">
        <article>
          <p>Function: the site name is GEEKINNEY BLOG</p>
          <p>Filter: the site name is Geekinney blog</p>
          <p>Happy hacking emacs!</p>
        </article>
        <div id="comment">
          <p>this is disqus block</p>
        </div>
      </div>
      <div class="col-md-4 col-lg-4">
        <aside id="sidebar">
          <p>this is extended sidebar</p>
        </aside>
      </div>
    </div>
    <div class="row">
      <div class="col-12">
        <footer>
          <p>this is base footer.</p>
        </footer>
      </div>
    </div>
  </div>
</body>

4 说明

4.1 预览调试

pp-html-test 函数可以在view buffer中预览生成的格式化html。 pp-html-parse 函数可以查看解析完所有逻辑标签后的S表达式。这两个函数便于调试代码。

4.2 XML支持

pp-html还额外支持生成xml。与html不同,xml没有单元素(img,link…),所以更简单。使用方法为设置 pp-html 函数的第二个参数为t。

4.3 结合OrgMode

在Org文件中,使用带参数的emacs-lisp代码块可以在Org或HTML中生成elisp代码对应的HTML。例如:

1.当导出Org文件时,生成一个有红色背景div的html页面。

#+BEGIN_SRC emacs-lisp :results value html :exports results
(pp-html '(div :style "background-color:red;" "content"))
#+END_SRC

#+RESULTS:
#+begin_export html
<div style="background-color:red;">content</div>
#+end_export

2.当导出Org文件时,生成包含 <div style="background-color:red;">content</div> 代码的html页面。

#+BEGIN_SRC emacs-lisp :wrap src html :exports results
(pp-html '(div :style "background-color:red;" "content"))
#+END_SRC

#+RESULTS:
#+begin_src html
<div style="background-color:red;">content</div>
#+end_src

关于OrgMode导出代码块的参数设置参考 Working-with-Source-Code

4.4 构建博客

我的 个人博客 就是基于 pp-html 构建的,我将构建博客的代码组织成了emacs包: geekblog ,目前处理代码优化整理阶段,敬请关注 我的Github 或博客。

5 计划

  • [ ] 内置更多有用的标签
  • [ ] 内置更多有用的过滤器。
  • [ ] pp-html-reverse: 反向解析html字符串为pp-html的S表达式形式。

内置过滤器和标签参考Liquid.

6 鸣谢

pp-html是我写的第一个emacs包。由于是新手,开发过程断断续续持续了一个多月的时间,其间遇到了许多的技术难题。特别感谢 Emacs-China社区 的同学们答疑解惑。

此package可能有不成熟的地方,希望读者诸君、emacs大牛批评指正。关于package功能的拓展和集成,也可以给我提建议(issue或博客留言)。

如果你觉得我的工作对你有所帮助,欢迎 Star 此代码仓库!

发布于 2020-05-10

Emacs Hack - 视频压缩及格式(gif)转换

MacOS系统使用mov作为默认视频格式,mov格式的缺点是文件体积大,不便传输与使用。大多数时候我需要将视频压缩为mp4格式。同时,写文档时经常需要使用gif图片展示hack效果,所以将视频转为gif也成了硬需求之一。关于视频的压缩与格式转换,有很多在线工具以及app可以实现,但我更钟爱命令行工具。因为命令行比封装好的ui使用方便,可选参数多。最重要的是,可以和emacs结合。

ffmpeg 是一个非常优秀的视频处理命令,MacOS下可使用homebrew安装。做了下小hack,以后就可以在emacs中处理视频文件,生成gif啦。

(defun my/video-compress-and-convert (video new)
  (interactive "fvideo path: \nfnew item name (eg. exam.mp4, exam.gif) : ")
  (let ((extension (cadr (split-string (file-name-nondirectory new) "\\."))))
    (if (string= extension "gif")
        (progn
          (shell-command
           (concat "ffmpeg -i " video " -r 5 " new))
          (message "%s convert to %s successfully!" video new))
      (progn
        (shell-command
         (concat "ffmpeg -i " video " -vcodec libx264 -b:v 5000k -minrate 5000k -maxrate 5000k -bufsize 4200k -preset fast -crf 20 -y -acodec libmp3lame -ab 128k " new))
        (message "%s compress and convert to %s successfully!" video new))
      )
    ))

除此之外, ffmpeg 的功能远不止这些,还支持视频的录制、剪辑合并、加滤镜和音频处理等,读者可以访问 ffmpeg官网 探索。

发布于 2020-02-20

Emacs Lisp 编程总结

原文参考: Elisp Programming

1 lisp介绍

Lisp(历史上拼写为LISP)是具有悠久历史的计算机编程语言家族,有独特和完全括号的前缀符号表示法。起源于公元1958年,是现今第二悠久而仍广泛使用的高端编程语言。只有FORTRAN编程语言比它更早一年。Lisp编程语族已经演变出许多种方言。现代最著名的通用编程语种是Clojure、Common Lisp和Scheme。

Lisp最初创建时受到阿隆佐·邱奇的lambda演算的影响,用来作为计算机程序实用的数学表达。因为是早期的高端编程语言之一,它很快成为人工智能研究中最受欢迎的编程语言。在计算机科学领域,Lisp开创了许多先驱概念,包括:树结构、自动存储器管理、动态类型、条件表达式、高端函数、递归、自主(self-hosting)编译器、读取﹣求值﹣输出循环(英语:Read-Eval-Print Loop,REPL)。

"LISP"名称源自“列表处理器”(英语:LISt Processor)的缩写。列表是Lisp的主要数据结构之一,Lisp编程代码也同样由列表组成。因此,Lisp程序可以把源代码当作数据结构进行操作,而使用其中的宏系统,开发人员可将自己定义的新语法或领域专用的语言,嵌入在Lisp编程中。

代码和数据的可互换性为Lisp提供了立即可识别的语法。所有的Lisp程序代码都写为S-表达式或以括号表示的列表。函数调用或语义形式也同样写成列表,首先是函数或操作符的名称,然后接着是一或多个参数:例如,取三个参数的函数f即为(f arg1 arg2 arg3)。

Lisp语言的主要现代版本包括Common Lisp, Scheme,Racket以及Clojure。1980年代盖伊·史提尔二世编写了Common Lisp试图进行标准化,这个标准被大多数解释器和编译器所接受。还有一种是编辑器Emacs所派生出来的Emacs Lisp(而Emacs正是用Lisp作为扩展语言进行功能扩展)非常流行,并创建了自己的标准。

2 Elisp

2.1 概览

2.1.1 运行emacs-lisp的几种方式

key command description
C-x C-e eval-last-sexp 在S表达式结尾运行,在minibuffer显示结果
C-j eval-print-last-sexp 在S表达式结尾运行,打印运行结果
M-: eval-expression 在minibuffer输入命令并执行
M-x ielm ielm 使用IELM解释器运行代码

2.1.2 创建命令(interactive函数)

;; example
(defun buffer/insert-filename ()
  "Insert file path of current buffer at current point"
  (interactive)
  (insert (buffer-file-name (current-buffer))))

2.1.3 emacs探索

key command description
C-h k describe-key 运行命令后,继续按键,查看此时按键绑定的函数
C-h b describe-bindings 在*Help*界面搜索 Major Mode Bindings: 可以查看所有与当前major mode相关的按键。
C-h f describe-function 查看函数文档及详细代码

2.2 elisp编程的基本设置

三个有用的pcakage:

  • rainbow-delimiters: 不同颜色区分不同层级的括号
  • paredit: 检查括号匹配
  • company: elisp代码补全

2.3 基本运算

2.3.1 算术

ELISP> (+ 20 30)
50
ELISP> (- 100 80)
20
ELISP> (+ 1 2 3 4 5 6)
21
ELISP> (* 1 2 3 4 5 6)
720
ELISP> (/ 1 100)
0

ELISP> (> 10 1) ;; ?? 10 > 1
t
ELISP> (< 2 8) ;; ?? 2 < 8
t
ELISP> (< 8 2) ;; ?? 8 < 2
nil

ELISP> (= 2 2)
t
ELISP> (= 2 4)
nil

ELISP> (/= 2 2)
nil
ELISP> (exp -1)
0.36787944117144233
ELISP> (log 10)
2.302585092994046
ELISP> (sin pi)
1.2246467991473532e-16
ELISP> (cos pi)
-1.0
ELISP> (tan (/ pi 2))
1.633123935319537e+16
ELISP>

2.3.2 比较

;;;; Compare Numbers
ELISP> (= 2 (+ 1 1))
t

;;; Compare Symbols and Numbers
ELISP> (eq 1 1)
t
ELISP> (eq 1 2)
nil
ELISP>

ELISP> (eq 'x 'x)
t
ELISP>

;;; Compare Elements of a List
ELISP> (equal (list 1 2 3 4) (list 1 2 3 4))
t

;;; Compare Strings
ELISP> (string= "hello" "hello")
t

2.3.3 列表

ELISP> '(10 20 30 40)
(10 20 30 40)

ELISP> '(10 203 40 "hello" () ("empty" 65))
(10 203 40 "hello" nil
    ("empty" 65))

2.4 类型判断和Literals

2.4.1 Emacs Literals

;;; Numbers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ELISP> 1e3
1000.0

;;; String
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ELISP> "Hello World Emacs Literals"
"Hello World Emacs Literals"
ELISP>

;;; Symbol
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> 'this-a-symbol
this-a-symbol

ELISP> 'vector->list
vector->list

ELISP> 'symbol?
symbol\?
ELISP>

;; Boolean t and nil
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ELISP> t
t
ELISP> nil
nil
ELISP>

 ;;; Everything that is not "nil" is true:
 ;;-----------------------------------------
ELISP> (if t "It is true (not nil)" "It is false (it is nil)")
"It is true (not nil)"
ELISP>
ELISP> (if 100e3 "It is true (not nil)" "It is false (it is nil)")
"It is true (not nil)"
ELISP> (if '(a b c d)  "It is true (not nil)" "It is false (it is nil)")
"It is true (not nil)"
ELISP>

ELISP> (if nil  "It is true (not nil)" "It is false (it is nil)")
"It is false (it is nil)"
ELISP>

;;; Pair / Cons Cell
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ELISP> '(a . b)
(a . b)

ELISP> '(a . 2999)
(a . 2999)

;;; List
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ELISP> '(1 2 3 (3 4) (5 6 (+ 3 4)) 10 'a 'b "hello" )
(1 2 3
   (3 4)
   (5 6
      (+ 3 4))
   10 'a 'b "hello")

ELISP> '(+ 1 2 3 4 5)
(+ 1 2 3 4 5)

ELISP> '(cos 10)
(cos 10)

;;; Vectors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ELISP> [1 2 3 4 (+ 1 2 3 54)]
[1 2 3 4
   (+ 1 2 3 54)]

2.4.2 基本类型判断

Type Predicate Literal Description
Nil null nil '() Test if argument is nil
Numbers numberp 100, 200e3 Test if it is number.
String stringp "hello" Test if it is string
Symbol symbolp 'sym :keyworkd Test if it is a symbol.
       
Atom atom 'x "h" :key 200 Everything that is not a list or pair is an atom.
List listp '(1 2 x y) Test if it is a list
Pair consp '(a . 200) Test if it is a pair (cons cell)
Vector vectorp [1 200 'sym] Test if it is a vector
Object Predicate
Buffer bufferp
Window windowp
Frame framep
Process processp
ELISP> (null nil)
t
ELISP>
ELISP> (null '())
t

ELISP> (null 10)
nil

ELISP> (atom 10)
t
ELISP> (atom '(a . b))
nil
ELISP> (atom "hello world")
t
ELISP>

ELISP> (bufferp (current-buffer))
t
ELISP> (bufferp (selected-window))
nil
ELISP> (windowp (selected-window))
t
ELISP>

2.4.3 获取对象类型

ELISP> (type-of (current-buffer))
buffer
ELISP>
ELISP> (type-of (selected-window))
window
ELISP>

ELISP> (equal 'buffer (type-of (current-buffer)))
t
ELISP> (equal 'buffer (type-of (selected-window)))
nil
ELISP>

2.5 变量定义

;;; Constants
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (defconst zsh-shell "/usr/bin/zsh")
zsh-shell

ELISP> zsh-shell
"/usr/bin/zsh"
ELISP>

;;; Define a variable
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;; Set is not used very much
;;
ELISP> (set 'avar "hello world")
"hello world"

ELISP> avar
"hello world"
ELISP>

;;;;; The most used command for assignment is setq
;;
ELISP> (setq x 10)
10

ELISP> (setq avar "hello world")
"hello world"

ELISP> x
10

ELISP> avar
"hello world"
ELISP>

ELISP> (setq my-list '(10 20 30 40))
(10 20 30 40)

ELISP> my-list
(10 20 30 40)

;;; Multiple Assignment
;;
ELISP> (setq a 10 b 20 c "Emacs")
"Emacs"
ELISP> a
10
ELISP> b
20
ELISP> c
"Emacs"
ELISP>

;; Dynamic Scoping  (Local Variables)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
ELISP> (let ((x 1) (y 10)) (+ (* 4 x) (* 5 y)) )
54
ELISP> x
    ** Eval error **  Symbol's value as variable is void: x
ELISP> y
    ** Eval error **  Symbol's value as variable is void: y
ELISP>

2.6 函数定义

2.6.1 定义简单函数

语法: (defun <function name> (<parameters>) (<body>))

ELISP> (defun afunction (a b c) (+ a b c))
afunction

ELISP> (afunction 10 20 30)
60

ELISP> (defun myfun () (message "Hello Emacs"))
myfun
ELISP> (myfun)
"Hello Emacs"
ELISP>


ELISP>
ELISP> (defun signum (n)
     (cond ((> n 0) 1 )
       ((< n 0) -1)
       (0)))
signum
ELISP> (signum 10)
1
ELISP> (signum 0)
0
ELISP> (signum -23)
-1
ELISP>


ELISP> (defun factorial (n)
     (if (= n 0)
     1
     (* n (factorial (- n 1)))))
factorial

ELISP> (factorial 5)
120
ELISP

2.6.2 匿名函数/Lambda函数

语法: (lambda (<parameters>) (<body>))

ELISP> (lambda (x) (+ x 3))
(lambda
  (x)
  (+ x 3))

;;; Applying Lambda Functions
;;

ELISP> ((lambda (x) (+ x 3)) 4)
7
ELISP> (funcall (lambda (x) (+ x 3)) 4)
7
ELISP>

;;; Storing Lambda Function in Variable
;;
;;

ELISP> (defvar add3 (lambda (x) (+ x 3)))
add3


ELISP> add3
(lambda
  (x)
  (+ x 3))

ELISP> (funcall add3 10)
13

ELISP> (add3 10)
    ** Eval error **  Symbol's function definition is void: add3

ELISP> (funcall #'add3 10)
    ** Eval error **  Symbol's function definition is void: add3
ELISP>

;;; Passing Lambda Function to functions
;;
ELISP> (mapcar (lambda (x) (+ x 3))  '(1 2 3 4 5))
(4 5 6 7 8)

2.6.3 函数作为参数

语法: (caller-function #'<function-1> #'<function-1> arg1 arg2 …)

在函数内部,使用 funcall 调用函数作为参数

ELISP> (mapcar log '(1 10 100 1000))
    ** Eval error **  Symbol's value as variable is void: log


ELISP> (mapcar #'log10 '(1 10 100 1000))
(0.0 1.0 2.0 3.0)

(defun sum-fun (f1 f2 x)
  (+ (funcall f1 x) (funcall f2 x)))

ELISP> (sum-fun #'log #'exp 3)
21.18414921185578
ELISP>

ELISP> (+ (log 3) (exp 3))
21.18414921185578
ELISP>

ELISP> (sum-fun (lambda (x) (* 3 x))
    (lambda (x) (* 4 x))
    5)
35
ELISP>

ELISP> (defun 1+ (x) (+ 1 x))
1+
ELISP> (defun 3* (x) (* 3 x))
3*

ELISP> (sum-fun #'1+  #'3* 4)
17
ELISP>

ELISP> (sum-fun #'1+  (lambda (x) (* 3 x)) 4)
17
ELISP>

2.6.4 多参函数

(defun sum (&rest numbers)
  (apply #'+ numbers))

ELISP> (sum 1 2 3 4 5 6)
21


ELISP> (apply #'sum '(1 2 3 5 6))
17

ELISP> (apply #'sum (list 1 2 3 5 (+ 6 5 2)))
24

ELISP> (apply #'sum '())
0

ELISP> (apply #'sum nil)
0

ELISP> (sum nil)
    ** Eval error **  Wrong type argument: number-or-marker-p, ni

;;----------------------------------

(defun sum-prod (a &rest xs)
  (* a (apply #'+ xs)))


ELISP> (sum-prod 3 1 2 3 4 5)
45

ELISP> (sum-prod 1 1 2 3 4 5)
15

2.6.5 可选参数函数

(defun test-optional (a &optional b)
  (list a b))

ELISP> (test-optional 10 20)
(10 20)

ELISP> (test-optional 10 )
(10 nil)

;--------------------------------;

(defun test-optional2 (a b &optional b c d e)
  (list :a a :b b :c c :d d :e e))

ELISP> (test-optional2 0 1 2 3 4 5 )
(:a 0 :b 2 :c 3 :d 4 :e 5)


ELISP> (test-optional2 0 1 2 3 4  )
(:a 0 :b 2 :c 3 :d 4 :e nil)

ELISP> (test-optional2 0 1 2 3   )
(:a 0 :b 2 :c 3 :d nil :e nil)

ELISP> (test-optional2 0 1 2    )
(:a 0 :b 2 :c nil :d nil :e nil)

ELISP> (test-optional2 0 1  )
(:a 0 :b nil :c nil :d nil :e nil)

ELISP> (test-optional2 0 1)
(:a 0 :b nil :c nil :d nil :e nil)

;--------------------------------;

(defun test-optional-default-b (a &optional b)
  (if b
      (list a b)
      (list a "b is null")))

ELISP> (test-optional-default-b 1 2)
(1 2)

ELISP> (test-optional-default-b 1)
(1 "b is null")

ELISP> (test-optional-default-b 1 nil)
(1 "b is null")

2.6.6 含属性列表参数函数

(defun make-shell-interface (&rest params)
  "
  Create a shell interface.

  Possible parameters:

    :name      Name of shell
    :type      ['sh, 'bash, ...]
    :path      Path to program
    :buffer    Name of buffer

  "
  (let
       ((name   (plist-get params :name ))
    (type   (plist-get params :type))
    (path   (plist-get params :path))
    (buffer (plist-get params :buffer)))
    (list
     (cons 'name buffer)
     (cons 'type type)
     (cons 'path path)
     (cons 'buffer buffer))))


ELISP> (make-shell-interface :name "pylaucher" :path "/usr/bin/python" :type 'sh :buffer "pyshell")
((name . "pyshell")
 (type . sh)
 (path . "/usr/bin/python")
 (buffer . "pyshell"))

ELISP> (make-shell-interface :name "pylaucher" :path "/usr/bin/python" :type 'sh)
((name)
 (type . sh)
 (path . "/usr/bin/python")
 (buffer))

ELISP> (make-shell-interface :name "pylaucher" :path "/usr/bin/python" :type 'bash)
((name)
 (type . bash)
 (path . "/usr/bin/python")
 (buffer))

ELISP> (make-shell-interface :name "pylaucher" :path "/usr/bin/python")
((name)
 (type)
 (path . "/usr/bin/python")
 (buffer))

ELISP> (make-shell-interface :name "pylaucher" )
((name)
 (type)
 (path)
 (buffer))

ELISP> (make-shell-interface  )
((name)
 (type)
 (path)
 (buffer))

ELISP> (make-shell-interface :buffer "pyshell"  :path "/usr/bin/python" :type 'sh :name "pylaucher")
((name . "pyshell")
 (type . sh)
 (path . "/usr/bin/python")
 (buffer . "pyshell"))

2.6.7 Closures

elisp方言默认不支持closure,所以下面的代码不会像Scheme或Common Lisp一样执行。

参考:

(defun make-adder (x)
  (lambda (y) (+ x y)))


ELISP>
ELISP> (make-adder 3)
(lambda
  (y)
  (+ x y))

ELISP> ((make-adder 3) 4)
    ** Eval error **  Invalid function: (make-adder 3)
ELISP> (funcall (make-adder 3) 4)
    ** Eval error **  Symbol's value as variable is void: x
ELISP> (map (make-adder 3) '(1 2 3 4 5))
    ** Eval error **  Symbol's value as variable is void: x
ELISP>

支持closure的代码:

(setq lexical-binding t)

(defun make-adder (x)
  (lambda (y) (+ x y)))

ELISP> (make-adder 3)
(closure
 ((x . 3)
  t)
 (y)
 (+ x y))

ELISP> ((make-adder 3) 4)
    ** Eval error **  Invalid function: (make-adder 3)
ELISP>

ELISP> (funcall (make-adder 3) 4)
7
ELISP>

ELISP> (mapcar (make-adder 3) '(1 2 3 4 5))
(4 5 6 7 8)


;;;; Sometimes is better to create macro rather than a higher order function


(defmacro make-sum-fun (f1 f2)
  `(lambda (x) (+ (,f1 x) (,f2 x))))

ELISP>
ELISP> (funcall (make-sum-fun sin cos) 3)
-0.8488724885405782
ELISP>
ELISP> (make-sum-fun sin cos)
(closure
 (t)
 (x)
 (+
  (sin x)
  (cos x)))

ELISP> (map (make-sum-fun sin cos) '(1 2 3 4 5))
(1.3817732906760363 0.4931505902785393 -0.8488724885405782 -1.4104461161715403 -0.6752620891999122)

~/.emacs.d/init.el 中添加如下配置以支持closure.

(setq lexical-binding t)

2.7 列表操作

参考:

;; Defining a List
;;
;; An emacs list can contain elements of almost any type.
;;
ELISP> '( "a" 2323 "b" 21.2323 "hello" "emacs" nil () (34 134) '(+ 2 3 5))
("a" 2323 "b" 21.2323 "hello" "emacs" nil nil
 (34 134)
 '(+ 2 3 5))

ELISP> (quote (1 3 3 4 5))
(1 3 3 4 5)

;;;;; Empty List
;;
ELISP> nil
nil
ELISP> '()
nil
ELISP>

;; Length of a list
ELISP> (length '(1 2 3 4 5 6))
6
ELISP>


;; nth element of a list
;;
ELISP> (nth 0 '(0 1 2 3 4 5))
0
ELISP> (nth 2 '(0 1 2 3 4 5))
2
ELISP> (nth 5 '(0 1 2 3 4 5))
5
ELISP> (nth 10 '(0 1 2 3 4 5))
nil
ELISP>


;; Membership test
;; member returns null if the element is not member of the list
;;
ELISP> (member 2 '(0 1 2 3 4 5))
(2 3 4 5)

ELISP> (member 10 '(0 1 2 3 4 5))
nil
ELISP>

;; Position of list element (prior to emacs 24.4)
;;
ELISP> (position 7 '(5 6 7 8))
2

ELISP> (position 17 '(5 6 7 8))
nil
ELISP>

;; Position of list element (emacs 24.4 or later)
;;
ELISP> (cl-position 7 '(5 6 7 8))
2

ELISP> (cl-position 17 '(5 6 7 8))
nil
ELISP>

;; cdr
;;
;; Removes first element of the list, returns the list tail.
;;
ELISP> (cdr '(1 2 3 4 5))
(2 3 4 5)

;; car
;;
;; Returns the first list element
;;
ELISP> (car '(1 2 3 4 5))
1
ELISP>


;; cons
;;
;; List constructor
;;
ELISP> (cons 10 '(1 2 3 4))
(10 1 2 3 4)

ELISP> (cons 1 (cons 2 (cons 3 (cons 4 (cons 5 '())))))
(1 2 3 4 5)

;; Last element of a list
;;
;;
ELISP> (car (last '(1 2 3 4 5)))
5
ELISP>


;; Reverse a list
;;
ELISP> (reverse '(1 2 3 4 5))
(5 4 3 2 1)


;; Append lists
;;
;; Note: nil also means an empty list
;;
ELISP> (append '(1 2) '( "a" "b" "c" "d"))
(1 2 "a" "b" "c" "d")

ELISP> (append '(1 2) nil '( "a" "b" "c" "d") nil)
(1 2 "a" "b" "c" "d")



;; Filter list elements given a predicate function
;;
;;
ELISP> (remove-if-not (lambda (x) (> x 2)) '(1 2 3 4 5 6 7 8 9 10))
(3 4 5 6 7 8 9 10)

;; Test if list is empty
;;
ELISP> (null '(1 2 3 4 5))
nil
ELISP> (null '())
t
ELISP> (null nil)
t
ELISP>

;; Drop the firsts n elements of a list
;;
;;
ELISP> (nthcdr 2 '(1 2 3 4))
(3 4)

ELISP> (nthcdr 3 '(1 2 3 4))
(4)

ELISP> (nthcdr 13 '(1 2 3 4))
nil
ELISP>

;; Delete an element of a list
;;
;;
ELISP> (delq 1 '(1 2 3 4))
(2 3 4)


ELISP> (delq 10 '(1 2 3 4))
(1 2 3 4)

;; It doesn't work to delete sublists
;;
ELISP> (delq (5) '(1 2 (5) 3 4))
    ** Eval error **  Invalid function: 5
ELISP> (delq '(5) '(1 2 (5) 3 4))
(1 2
   (5)
   3 4)

ELISP> (delete '(5) '(1 2 (5) 3 4))
(1 2 3 4)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Convert Vector to List
;;
;;
ELISP> (coerce [1 2 3] 'list)
(1 2 3)

;; Convert List to Vector
;;
ELISP> (coerce '(1 2 3) 'vector)
[1 2 3]

ELISP> (number-sequence 0 10 2)
(0 2 4 6 8 10)

ELISP> (number-sequence 9 4 -1)
(9 8 7 6 5 4)


;; Modify list variables.
;;
ELISP> alist
(a b c d e)

ELISP> (push 'f alist)
(f a b c d e)

ELISP> alist
(f a b c d e)

ELISP> (pop alist)
f

ELISP> alist
(a b c d e)

ELISP> (pop alist)
a
ELISP> alist
(b c d e)

ELISP>

2.8 关联列表和属性列表

2.8.1 概览

关联列表是一系列cons对,这里我可以称作 clist 或者 由两个元素组成的列表的集合,可以称为 alist

关联列表类型:clist

键: a, x, 2 and 4 值: b, y, 3 and (1 2 3 4 5)

ELISP> '((a . b) (x . y) (2 . 3) (4 . (1 2 3 4 5)))
((a . b)
 (x . y)
 (2 . 3)
 (4 1 2 3 4 5)

ELISP> (cons 'a 'b)
(a . b)

ELISP> (cons 'a (cons 'b (cons 'c nil)))
(a b c)

关联列表类型:alist

ELISP> '((a  b) (x  y) (2  3) (4  (1 2 3 4 5)))
((a b)
 (x y)
 (2 3)
 (4
  (1 2 3 4 5)))

ELISP> (list (list 'a 'b) (list 'x 'y) (list 2 3) (list 2 '(1 2 3 4 5)))
((a b)
 (x y)
 (2 3)
 (2
  (1 2 3 4 5)))

alist 不像 clist 有歧义。

属性列表:Plist

属性列表是连续的键值对集合,它的优势是括号少和可读性高。

'(:key1 value1 :key2 value2 :key3 1002.23 :key4 (a b c d e))

ELISP> '(:key1 value1 :key2 value2 :key3 1002.23 :key4 (a b c d e))
(:key1 value1 :key2 value2 :key3 1002.23 :key4
       (a b c d e))

;;; It is more useful in configuration files

(
:key1  value1
:key2  value2
:key3  value3
:key4  (a b c d e )
)

2.8.2 关联列表/Alist

ELISP> (setq dict
'((pine . cones)
 (oak . acorns)
 (maple . seeds)))
((pine . cones)
 (oak . acorns)
 (maple . seeds))

ELISP> dict
((pine . cones)
 (oak . acorns)
 (maple . seeds))

;; Get a cell associated with a key
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ELISP>
ELISP> (assoc 'oak dict)
(oak . acorns)

ELISP> (assoc 'wrong dict)
nil

;; Get a Key
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (car (assoc 'oak dict))
oak
ELISP> (cdr (assoc 'oak dict))
acorns
ELISP>


ELISP> (car (assoc 'oak dict))
oak
ELISP>

;; Get all keys
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (mapcar #'car dict)
(pine oak maple)

;; Get all values
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (mapcar #'cdr dict)
(cones acorns seeds)

例:过滤多个键

ELISP> (defvar language-list
  '(
   ("io" . ((:command . "io")
         (:description . "Run IO Language script")))
    ("lua" . ((:command . "lua")
          (:description . "Run Lua script")))
    ("groovy" . ((:command . "groovy")
         (:description . "Run Groovy")))
    ("scala" . ((:command . "scala")
        (:cmdopt . "-Dfile.encoding=UTF-8")
        (:description . "Run Scala file with scala command")))

    ("haml" . ((:command . "haml")
           (:exec    . "%c %o %s")
           (:description . "Convert HAML to HTML")))
    ("sass" . ((:command . "sass")
           (:exec    . "%c %o --no-cac")))
 ))
language-list


ELISP> (assoc  "scala"  language-list )
("scala"
 (:command . "scala")
 (:cmdopt . "-Dfile.encoding=UTF-8")
 (:description . "Run Scala file with scala command"))

ELISP> (assoc  "lua"  language-list )
("lua"
 (:command . "lua")
 (:description . "Run Lua script"))

ELISP> (assoc  "wrong"  language-list )
nil

ELISP> (assoc ':command (assoc  "scala"  language-list ))
(:command . "scala")

ELISP> (cdr (assoc ':command (assoc  "scala"  language-list )))
"scala"
ELISP>

ELISP> (assoc ':description (assoc  "scala"  language-list ))
(:description . "Run Scala file with scala command")

ELISP> (cdr (assoc ':description (assoc  "scala"  language-list )))
"Run Scala file with scala command"
ELISP>

ELISP> (mapcar 'car language-list)
("io" "lua" "groovy" "scala" "haml" "sass")

ELISP> (mapcar 'cdr language-list)
(((:command . "io")
  (:description . "Run IO Language script"))
 ((:command . "lua")
  (:description . "Run Lua script"))
 ((:command . "groovy")
  (:description . "Run Groovy"))
 ((:command . "scala")
  (:cmdopt . "-Dfile.encoding=UTF-8")
  (:description . "Run Scala file with scala command"))
 ((:command . "haml")
  (:exec . "%c %o %s")
  (:description . "Convert HAML to HTML"))
 ((:command . "sass")
  (:exec . "%c %o --no-cac")))

ELISP>

ELISP> (mapcar (lambda (x) (
                 list
                 (car x)
                 (cdr x)
                 ))
                language-list)
(("io"
  ((:command . "io")
   (:description . "Run IO Language script")))
 ("lua"
  ((:command . "lua")
   (:description . "Run Lua script")))
 ("groovy"
  ((:command . "groovy")
   (:description . "Run Groovy")))
 ("scala"
  ((:command . "scala")
   (:cmdopt . "-Dfile.encoding=UTF-8")
   (:description . "Run Scala file with scala command")))
 ("haml"
  ((:command . "haml")
   (:exec . "%c %o %s")
   (:description . "Convert HAML to HTML")))
 ("sass"
  ((:command . "sass")
   (:exec . "%c %o --no-cac"))))

ELISP>

ELISP> (mapcar (lambda (x) (
     list
     (car x)
     (assoc ':command       (cdr x))
     (assoc ':cmdopt        (cdr x))
     (assoc ':description   (cdr x))
     ))
    language-list)

(("io"
  (:command . "io")
  nil
  (:description . "Run IO Language script"))
 ("lua"
  (:command . "lua")
  nil
  (:description . "Run Lua script"))
 ("groovy"
  (:command . "groovy")
  nil
  (:description . "Run Groovy"))
 ("scala"
  (:command . "scala")
  (:cmdopt . "-Dfile.encoding=UTF-8")
  (:description . "Run Scala file with scala command"))
 ("haml"
  (:command . "haml")
  nil
  (:description . "Convert HAML to HTML"))
 ("sass"
  (:command . "sass")
  nil nil))

ELISP>


ELISP> (mapcar (lambda (x) (
     list
     (car x)
     (cdr (assoc ':command   (cdr x)))
     (cdr (assoc ':cmdopt       (cdr x)))
     (cdr (assoc ':description   (cdr x)))
     ))

    language-list)
(("io" "io" nil "Run IO Language script")
 ("lua" "lua" nil "Run Lua script")
 ("groovy" "groovy" nil "Run Groovy")
 ("scala" "scala" "-Dfile.encoding=UTF-8" "Run Scala file with scala command")
 ("haml" "haml" nil "Convert HAML to HTML")
 ("sass" "sass" nil nil))

ELISP>

ELISP> (defun get-value (alist key) (cdr (assoc key alist)))
get-value
ELISP> (get-value language-list "scala")
((:command . "scala")
 (:cmdopt . "-Dfile.encoding=UTF-8")
 (:description . "Run Scala file with scala command"))

ELISP> (get-value language-list "lua")
((:command . "lua")
 (:description . "Run Lua script"))

ELISP>
ELISP> (get-value language-list "0")
nil
ELISP>


ELISP> (defun get-key-value (alist key field)
        (cdr (assoc  field  (cdr (assoc key alist))  )))
get-key-value
ELISP>
ELISP> (get-key-value language-list "scala" ':description)
"Run Scala file with scala command"
ELISP>

ELISP> (get-key-value language-list "scala" ':command)
"scala"
ELISP>

2.8.3 属性列表

ELISP> (defvar plst (list :buffer (current-buffer) :line 10 :pos 2000))
plst

ELISP>
ELISP> (plist-get plst :line)
10

ELISP> (plist-get plst :pos)
2000

ELISP> (plist-get plst :buffer)
#<buffer *ielm*>
ELISP>

ELISP>
ELISP> (plist-get plst :buffdfds)
nil
ELISP>

ELISP> (plist-member plst :buffer)
(:buffer #<buffer *ielm*> :line 10 :pos 2000)

ELISP> (plist-member plst :bufferasd)
nil
ELISP>

ELISP> (plist-put plst :winconf (current-window-configuration))
(:buffer #<buffer *ielm*> :line 10 :pos 2000 :winconf #<window-configuration>)

ELISP> plst
(:buffer #<buffer *ielm*> :line 10 :pos 2000 :winconf #<window-configuration>)

ELISP>

2.8.4 转换Alist成Plist和vice-versa

;; Alist to plist
(defun plist->alist (plist)
  (if (null plist)
      '()
      (cons
       (list (car plist) (cadr plist))
       (plist->alist (cddr plist)))))

ELISP> (plist->alist (list :x 10 :y 20 :name "point"))
((:x 10)
 (:y 20)
 (:name "point"))

;;; Converts association list to plist
(defun alist->plist (assocl)
  (if (null assocl)
      '()
    (let
    ((hd (car assocl))
     (tl (cdr assocl)))
      (cons (car hd)
    (cons (cadr hd)
      (alist->plist tl))))))

;;; Converts plist to clist (List of cons pairs)
(defun plist->clist (plist)
  (if (null plist)
      '()
      (cons
       (cons (car plist) (cadr plist))
      (plist->clist (cddr plist)))))

ELISP> (plist->clist (list :x 10 :y 20 :name "point"))
((:x . 10)
 (:y . 20)
 (:name . "point"))

;; Separates a property list into two lists of keys and values.
;;
(defun plist->kv (plist)
  (let ((alist (plist->alist plist)))
    (cons
     (mapcar #'car alist)
     (mapcar #'cdr alist))))

ELISP> (setq al (plist->alist (list :x 10 :y 20 :name "point")))
((:x 10)
 (:y 20)
 (:name "point"))

ELISP> (alist->plist al)
(:x 10 :y 20 :name "point")

ELISP>

(setq keylist
    '("M-i"  'previous-line
      "M-j"  'backward-char
      "M-k"  'next-line
      "M-l"  'forward-char))


ELISP> (setq kv (plist->kv keylist))
(("M-i" "M-j" "M-k" "M-l")
 ('previous-line)
 ('backward-char)
 ('next-line)
 ('forward-char))

ELISP> (car kv)
("M-i" "M-j" "M-k" "M-l")

ELISP> (cdr kv)
(('previous-line)
 ('backward-char)
 ('next-line)
 ('forward-char))

ELISP>

2.9 字符串

;; Split String

ELISP> (split-string "  two words ")
("two" "words")

ELISP>

ELISP> (split-string "o\no\no" "\n" t)
("o" "o" "o")

ELISP> (split-string "Soup is good food" "o*" t)
("S" "u" "p" " " "i" "s" " " "g" "d" " " "f" "d")

ELISP>

;; Format String

ELISP> (format-time-string "%Y/%m/%d %H:%M:%S" (current-time))
"2015/06/26 06:10:04"
ELISP>
ELISP>


;; Concatenate Strings

ELISP> (concat "The " "quick brown " "fox.")
"The quick brown fox."
ELISP>

ELISP> (mapconcat 'identity '("aaa" "bbb" "ccc") ",")
"aaa,bbb,ccc"
ELISP> (split-string "aaa,bbb,ccc" ",")
ELISP> (split-string "aaa,bbb,ccc" ",")
("aaa" "bbb" "ccc")

;; String Width

ELISP> (string-width "hello world")
11
ELISP>
ELISP> (substring "Freedom Land" 0 5)
"Freed"
ELISP>
ELISP> (string-match "ce" "central park")
0
ELISP> (string-match "gt" "central park")
nil
ELISP>


;;;;; Misc

ELISP> (make-string 5 ?x)
"xxxxx"
ELISP> (make-string 5 ?a)
"aaaaa"
ELISP> (make-string 5 ?r)
"rrrrr"
ELISP> (make-string 15 ?r)
"rrrrrrrrrrrrrrr"
ELISP>

elisp符号/字符串转换

; Convert a symbol to string
ELISP> (symbol-name 'wombat)
"wombat"

; Convert a String to Symbol
ELISP> (intern "wombat")
wombat

读取字符串中的S表达式

ELISP> (read-from-string
        "(
           (POINT1  (X  10.2323)  (Y   20.2323))
           (POINT2  (x  0.2)          (Y 923.23))
           (POINT3  (x -10.5)       (Y 78,23))
         )")
(((POINT1
   (X 10.2323)
   (Y 20.2323))
  (POINT2
   (x 0.2)
   (Y 923.23))
  (POINT3
   (x -10.5)
   (Y 78
      (\, 23))))
 . 174)

ELISP>

2.10 符号

;;; Convert a string to symbol

ELISP> (intern "a-symbol")
a-synmbol
ELISP> (symbolp (intern "a-symbol"))
t
ELISP>

;;; Convert a symbol to a string

ELISP> (symbol-name 'symbol)
"symbol"
ELISP>

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (setq sym '(1 2 3 4 5))
(1 2 3 4 5)

ELISP> sym
(1 2 3 4 5)

;;; Test if variable is defined
ELISP> (boundp 'sym)
t
ELISP>

;;; Test if variable sym is a symbol
ELISP> (symbolp sym)
nil

;;; Test if the symbol sym is a symbol
ELISP> (symbolp 'sym)
t
ELISP>

;; Get symbol as string
;;
ELISP> (symbol-name 'sym)
"sym"

;; Get value from a symbol
;;
ELISP> (symbol-value 'sym)
(1 2 3 4 5)

ELISP> (symbol-function 'sym)
nil

ELISP> (symbol-plist 'sym)
nil

;;-------------------------;;

ELISP> (defun func (x y) (+ (* 3 x) (* 4 y)))
func

ELISP> (func 10 2)
38
ELISP>

;;; Check if function is defined
;;
ELISP> (fboundp 'func)
t
ELISP> (fboundp 'sym)
nil
ELISP>


ELISP> (symbol-name 'func)
"func"

ELISP> (symbol-value 'func)
    ** Eval error **  Symbol's value as variable is void: func
ELISP> (symbol-function 'func)
(lambda
  (x y)
  (+
   (* 3 x)
   (* 4 y)))

ELISP> (symbol-plist 'func)
nil
ELISP>

;;; Function Source Code

ELISP> (symbol-function #'func)
(lambda
  (x y)
  (+
   (* 3 x)
   (* 4 y)))


;; Test if function is an elisp primitive

ELISP> (subrp (symbol-function 'goto-char))
t
ELISP>

2.11 类型转换

类型查询

ELISP> (type-of 1000)
integer

ELISP> (type-of 1000.3434)
float
ELISP>

ELISP> (type-of "lisp")
string

ELISP> (type-of '(1 2 3 4 5))
cons
ELISP> (type-of (list 'cos 'sin 1 2 3 4 5))
cons
ELISP>

ELISP> (type-of [1 2 3 4])
vector

ELISP> (type-of 'elisp-mode-map)
symbol
ELISP>

ELISP> (type-of #'cos)
symbol
ELISP>

类型判断

;; Test if it is a number
;;-----------------------------------

ELISP> (numberp 1000)
t
ELISP> (numberp 10e4)
t
ELISP> (numberp '(1 2 3 4))
nil
ELISP> (numberp "hello world")
nil
ELISP>


;; Test if it is a string
;;-----------------------------------

ELISP> (stringp "Emacs")
t
ELISP> (stringp '(1 2 3 4))
nil
ELISP>

;; Test if ti is a symbol
;;------------------------------------
ELISP> (symbolp 'emacs)
t
ELISP> (symbolp #'emacs)
t
ELISP> (symbolp "something")
nil
ELISP> (symbolp 10000)
nil
ELISP>


;; Test if it is a list
;;-----------------------------------

ELISP> (listp '(1 2 3 4))
t
ELISP> (listp [1 2 3 4])
nil
ELISP> (listp "hello world")
nil
ELISP>


;; Test if it is a vector
;;-----------------------------------

ELISP> (vectorp ["Lisp" "Emacs" "Scheme" "Clojure"])
t
ELISP>
ELISP> (vectorp '(1 2 3))
nil
ELISP> (vectorp "lisp")
nil
ELISP>

数字/字符串转换

ELISP>
ELISP> (number-to-string 1000)
"1000"

ELISP> (string-to-number "200")
200
ELISP>
ELISP>

符号/字符串转换

ELISP> (symbol-name 'my-symbol)
"my-symbol"

ELISP> (symbol-name :my-symbol)
":my-symbol"
ELISP>

ELISP> (intern "some-symbol")
some-symbol

S表达式/字符串转换

  • read: 解析S表达式
ELISP>
ELISP> (setq raw "(:x 10 :y 20 :z 30 :w \"hello world\")")
"(:x 10 :y 20 :z 30 :w \"hello world\")"
ELISP>
ELISP> (read raw)
(:x 10 :y 20 :z 30 :w "hello world")

ELISP> (plist-get (read raw) :x)
10
ELISP> (plist-get (read raw) :w)
"hello world"
ELISP>
  • prin1-to-string: 序列化S表达式
ELISP> (setq sexp '(:x 10 :y 20 :z 30 :w "hello world"))
(:x 10 :y 20 :z 30 :w "hello world")

ELISP> sexp
(:x 10 :y 20 :z 30 :w "hello world")

ELISP> (prin1-to-string sexp)
"(:x 10 :y 20 :z 30 :w \"hello world\")"
ELISP>

2.12 求值

S表达式求值

ELISP> (eval '(+ 1 2 3 4 5))
15
ELISP>


ELISP> '(defun func1(x)(* 10 x))
(defun func1
    (x)
  (* 10 x))

ELISP>

ELISP> '((+ 1 3) (* 4 5) (- 8 9))
((+ 1 3)
 (* 4 5)
 (- 8 9))

ELISP> (eval '(defun func1(x)(* 10 x)))
func1
ELISP> (func1 5)
50
ELISP>


ELISP> (mapcar 'eval '((+ 1 3) (* 4 5) (- 8 9)))
(4 20 -1)

字符串求值

ELISP> (defun eval-string (str) (eval (read str)))
eval-string

ELISP> (eval-string "(+ 1 2 3 4 5 6)")
21
ELISP>

ELISP> (eval-string "(defun func2(x)(* 10 x)))")
func2
ELISP> (func2 6)
60
ELISP>

S表达式格式化为字符串

ELISP> (setq sexp1 '(+ 1 (* 2 3)))
(+ 1
   (* 2 3))

ELISP> (eval sexp1)
7

ELISP> (format "%S" sexp1)
"(+ 1 (* 2 3))"
ELISP>

Elisp中的求值命令

命令 功能
M-x eval-defun 函数求值
M-x eval-region 区域内表达式求值
M-x eval-buffer buffer内表达式求值
M-x eval-expression 输入框输入求值
M-x load-file 文件加载

2.13 Defalias

内置宏 defalias 可以为emaca函数定义简短的名字。

参考:Emacs: Use Alias for Fast M-x

ELISP> (require 'cl)
cl
ELISP>

ELISP> (defalias 'map 'mapcar)
map
ELISP> (map (lambda (x) (* 3 x)) (list 1 2 3 4 5 6))
(3 6 9 12 15 18)

ELISP> (defalias 'filter 'remove-if-not) ;; remove-if-not comes from "cl" library
filter

;;; Filter all buffers bounded to a file
;;
ELISP> (filter #'buffer-file-name (buffer-list))
(#<buffer README.org> #<buffer Projects.wiki.org> #<buffer Index.wiki.org> #<buffer settings.org> #<buffer project.org>)

;;; Reject all buffers which are not bounded to a file
ELISP> (reject #'buffer-file-name (buffer-list))
(#<buffer *ielm*> #<buffer *Help*> #<buffer  *Minibuf-1*> #<buffer emacs> #<buffer *scratch*> ..)

;;; The command M-x org-html-export-to-htm will export this document (README.org) to html
;;  the command M-x org2html will do so too.
;;
(defalias #'org2html #'org-html-export-to-html)

;;
;;  It is also useful to create more convenient names for Emacs API
;; in a namsepace-like fashion that makes easier to find functions and
;; autocomplete functions, for instance:
;;
(defalias 'file/extension         'file-name-extension)
(defalias 'file/extension-sans    'file-name-sans-extension)
(defalias 'file/path-expand       'expand-file-name)
(defalias 'file/filename          'file-name-nondirectory)
(defalias 'file/path-relative     'file-relative-name)
(defalias 'file/rename            'rename-file)
(defalias 'file/delete            'delete-file)
(defalias 'file/copy              'copy-file)

;;; To find the documentation of a function group defined in this fashion
;; Enter M-x apropos  and then type file/
(apropos "file/")

ELISP> (set-buffer "README.org")
#<buffer README.org>
ELISP> (buffer-file-name)
"/home/tux/PycharmProjects/emacs/README.org"
ELISP> (file/basename (buffer-file-name))
"README"
ELISP> (file/extension (buffer-file-name))
"org"
ELISP> (file/filename (buffer-file-name))
"README.org"
ELISP>

2.14 控制结构

2.14.1 Conditional Statement

If Else 语句

;;
;; Any value different from nil or '() is true, otherwise false.
;;

;; True
;;
ELISP> (if t 5 6)
5

ELISP> (if 10 5 6)
5

ELISP> (if 0 5 6)
5

;; False
ELISP> (if nil 5 6)
6

ELISP> (if '() 5 6)
6


;; Inverting Predicate
;;
ELISP> (if (not t) 5 6)
6

ELISP> (if (not nil) 5 6)
5


ELISP> (if (< 5 10)  (message "less than 10") (message "greater or equal to 10") )
"less than 10"

ELISP> (if (< 30 10)  (message "less than 10") (message "greater or equal to 10") )
"greater or equal to 10"
ELISP>

;;; If else with multiple statements

ELISP> (setq x 10)
10

ELISP> (if (> x 5)
       ;; Then Statement
       (progn

     (message "Positive Number")
     (print "Greater than five")
     (split-window-vertically)
     78 ;;  Return Value
    )
     ;; Else Statement
     (progn
       (print "Less than five")
       (split-window-horizontally)
       12 ;;  Return Value
     ))

"Greater than five"

78
ELISP>

When语句

ELISP> (when t 3)
3

ELISP> (when nil 3)
nil


ELISP> (setq x 5)
5

ELISP> (when (> x 3)
     (message "Less than 3"))
"Less than 3"
ELISP>

ELISP> (setq x 1)
1

ELISP> (when (> x 3)
     (message "Less than 3"))
nil
ELISP>


;;;;; When with Multiple Statements

ELISP> (setq x 10)
10

ELISP> (when (> x 7)
     (progn
       (message "Greater than 7 OK.")
       (message "Print message 2")
       (split-window-horizontally)
      ))

 #<window 8 on *ielm*>
ELISP>

2.14.2 Cond - Case Switch

ELISP> (setq a 3)       ;; a = 3
3
ELISP>

ELISP> (cond
    ((evenp a) a)       ;; if   (a % 2 == 0)    ==> a
    ((> a 7) (/ a 2))   ;; elif (a > 7)         ==> a/2
    ((< a 5) (- a 1))   ;; elif (a < 5)         ==> a-1
    (t 7)               ;; else                 ==> 7
    )
2
ELISP>

2.14.3 CL-Case - Case Switch

(defun test-cl-case (operation x y)
  (cl-case operation
    (:mul (* x y))
    (:add (+ x y))
    (:sub (- x y))
    (:div (/ x y))
    (otherwise nil)))

ELISP> (test-cl-case :mul 2 10)
20

ELISP> (test-cl-case :sub 10 2)
8

ELISP> (test-cl-case :add 10 2)
12
ELISP> (test-cl-case :div 10 2)
5

ELISP> (test-cl-case 'dummy 20 10)
nil

2.14.4 循环

Dolist

ELISP> (dolist (h '(a b c)) (print h))

a

b

c

nil

ELISP> (dolist (x '(1 2 3)) (print (* 2 x)))

2

4

6

nil
ELISP>

ELISP> (dolist (x '(1 2 3))
     (dolist (y '(a b))
        (print (list x y))))
(1 a)

(1 b)

(2 a)

(2 b)

(3 a)

(3 b)

nil
ELISP>

Dotimes

ELISP> (dotimes (i 3) (print i))

0

1

2

nil
ELISP

ELISP> (dotimes (i 3) (print (* 2 i)))

0

2

4

nil
ELISP>

Loop

最好使用 mapfilter 代替 loops , 详见 Functional Programming

ELISP> (setq a 4)
4

ELISP> (loop
    (setq a (+ a 1))
    (when (> a 7) (return a)))
8

ELISP> a
8
ELISP>

ELISP> (loop
   (setq a (- a 1))
   (when (< a 3) (return)))
nil
ELISP> a
2
ELISP>

Loop Collecting / Summing / For

ELISP> (loop for i from 1 to 10 collecting i)
(1 2 3 4 5 6 7 8 9 10)

ELISP> (loop for i from 1 to 10 collecting (* 3 i))
(3 6 9 12 15 18 21 24 27 30)

ELISP> (loop for x from 1 to 10 summing (expt x 2))
385

ELISP> (loop for x from 1 to 10 collecting (* 2 x))
(2 4 6 8 10 12 14 16 18 20)

ELISP> (loop for x from 1 to 10 summing (* 2 x))
110
ELISP>

ELISP> (apply #'+ '(2 4 6 8 10 12 14 16 18 20))
110

ELISP> (loop for i below 10 collecting i)
(0 1 2 3 4 5 6 7 8 9)

ELISP>  (loop for x in '(1 2 3)
      do (print x) )

1

2

3

nil

(loop
       for x in '(a b c)
       for y in '(1 2 3 4 5 6)
       collect (list x y))
((a 1)
 (b 2)
 (c 3))

ELISP> (loop for (a b) in '((x 1) (y 2) (z 3))
      collect (list b a))
((1 x)
 (2 y)
 (3 z))

ELISP> (loop for i upto 20
      if (oddp i)
    collect i into odds
      else
    collect i into evens
      finally (return (values evens odds)))
((0 2 4 6 8 10 12 14 16 18 20)
 (1 3 5 7 9 11 13 15 17 19))

Do Loop

(do (variable-definition*)
    (end-test-form result-form*)
  statement*)
(do
   ;; Variables Definitions
   ((i 0 (1+ i)))

   ;; Test form
    ((>= i 4))

  ;; Statement form
  (print i))

0

1

2

3
nil

;; Fibbonaci Computing Loop
;;
(do ((n 0 (1+ n))
     (cur 0 next)
     (next 1 (+ cur next)))
    ((= 10 n) cur))
55

2.14.5 函数式编程

Dash 是emacs经常使用的函数式编程库。

  • Map and Filter

Mapcar / Equivalent to map

ELISP> (defun my-fun (x) (* x 10))
my-fun
ELISP>

ELISP> (mapcar 'my-fun '(1 2 3 5 6))
(10 20 30 50 60)

ELISP> (mapcar 'capitalize '("hello" "world" "emacs"))
("Hello" "World" "Emacs")

;;  Anonymous Functions
;;
ELISP> (mapcar (lambda (x) (* x x))   '(1 2 3 4 5 6))
(1 4 9 16 25 36)


ELISP> (setq anon (lambda (x) (* x x)))
(lambda
  (x)
  (* x x))

ELISP> (mapcar anon '(1 2 3 4 5 6))
(1 4 9 16 25 36)

Filter

ELISP> (null nil)
t
ELISP> (null 23)
nil
ELISP>

;; Equivalent to  Haskell idiom:
;;
;; > filter predicate list
;;
ELISP> (remove-if-not 'null '(1 2 3 nil 5 6 nil nil ))
(nil nil nil)

;; Equivalent to Haskell idiom:
;;
;;   > filter (\x -> not (predicate x)) list
;;
;; a more apropriate name would be reject
;;
ELISP> (remove-if 'null '(1 2 3 nil 5 6 nil nil ))
(1 2 3 5 6)



ELISP> (defun range (step start stop)
  (if (> start stop)
      nil
      (cons start (range step (+ step start) stop))

  );; End If
);; End range

ELISP> (range 1 0 10)
(0 1 2 3 4 5 6 7 8 9 10)

ELISP> (range 2 0 20)
(0 2 4 6 8 10 12 14 16 18 20)


ELISP> (remove-if (lambda (x) (= (% x 2) 0)) (range 1 0 20))
(1 3 5 7 9 11 13 15 17 19)

ELISP> (remove-if-not (lambda (x) (= (% x 2) 0)) (range 1 0 20))
(0 2 4 6 8 10 12 14 16 18 20)


ELISP> (remove-if (lambda (x) (= (% x 3) 0)) (range 1 0 20))
(1 2 4 5 7 8 10 11 13 14 16 17 19 20)

ELISP> (remove-if-not (lambda (x) (= (% x 3) 0)) (range 1 0 20))
(0 3 6 9 12 15 18)

ELISP>
  • 匿名函数/lambda函数
ELISP> (lambda (x)(* x 10))
(lambda
  (x)
  (* x 10))

ELISP>

ELISP> (funcall (lambda (x)(* x 10)) 5)
50
ELISP>

ELISP> (setq my-lambda (lambda (x) (+ (* x 10) 5))) ;; 10 * x + 5
(lambda
  (x)
  (+
   (* x 10)
   5))

ELISP> (funcall my-lambda 10)
105
ELISP> (mapcar my-lambda '(1 2 3 4 5))
(15 25 35 45 55)


ELISP>  (setq double (function (lambda (x) (+ x x)) ))
(lambda
  (x)
  (+ x x))

ELISP> (funcall double 22)
44
ELISP>


;;
;; Apply a function to a list of arguments
;;
;;;;;;;;;;;

ELISP> (apply #'+ '(1 2 3 4 5))
15
ELISP>

ELISP>
ELISP> (defun f (x y z) (+ (* 10 x) (* -4 y) (* 5 z)))
f
ELISP> (f 2 3 5)
33

ELISP> (apply 'f '(2 3 5))
33


ELISP> (mapcar (lambda (x) (apply 'f x)) '( (2 3 5) (4 5 6) (8 9 5)))
(33 50 69)



;; Create Higher Order Functions
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  • Function Composition ????
ELISP> ;; ID: f0c736a9-afec-3e3f-455c-40997023e130
(defun compose (&rest funs)
  "Return function composed of FUNS."
  (lexical-let ((lex-funs funs))
    (lambda (&rest args)
      (reduce 'funcall (butlast lex-funs)
          :from-end t
          :initial-value (apply (car (last lex-funs)) args)))))
          compose

ELISP> (funcall (compose 'prin1-to-string 'random* 'exp) 10)
"4757.245739507558"
ELISP>
  • Interactive Functions
(defun some-interactive-function ()
   "Documentation"
  (interactive)
  ...)
  • List Recursive Functions

Map

(defun map (fun xs)
  (if (null xs)
      '()
    (cons (funcall fun (car xs))
      (map fun (cdr xs)))))

ELISP> (map #'buffer-name (buffer-list))
("*ielm*" "*scratch*" " *Minibuf-1*" "*Backtrace*" "*eshell*" "sclj.import.scm" "*Messages*" "*GNU Emacs*" " *Minibuf-0*" " *code-conversion-work*" " *Echo Area 0*" " *Echo Area 1*" "*Shell Command Output*" "*Completions*")

ELISP>

Filter

(defun filter (fun xs)
  (if (null xs)
      '()
    (let ((hd (car xs))
      (tl (cdr xs)))
      (if (funcall fun hd)
      (cons hd (filter fun tl))
    (filter fun tl)))))

(defun odd? (x) (zerop (% x 2)))

ELISP> (filter #'odd? '(1 2 3 4 5 6))
(2 4 6)

Take

(defun take (n xs)
  (if (or (null xs) (zerop n))
      '()
    (cons (car xs)
          (take (- n 1) (cdr xs)))))


ELISP> (take 5 '(a b c d e f g h i j))
(a b c d e)

ELISP> (take 10 '(a b c d e f g h i j))
(a b c d e f g h i j)

ELISP> (take 200 '(a b c d e f g h i j))
(a b c d e f g h i j)

ELISP> (take 0 '(a b c d e f g h i j))
nil
ELISP> (take 10 '())
nil
ELISP>

Drop

(defun drop (n xs)
  (if (or (null xs) (zerop n))
      xs
    (drop (- n 1)  (cdr xs))))

ELISP> (drop 3 '(a b c d e f g h i j))
(d e f g h i j)

ELISP> (drop 4 '(a b c d e f g h i j))
(e f g h i j)

ELISP> (drop 25 '(a b c d e f g h i j))
nil
ELISP>

Map-apply

(defun map-apply (fun xss)
  (mapcar (lambda (xs) (apply fun xs)) xss))

ELISP> (map-apply #'fxyz '((1 2 3) (3 4 5) (2 3 1)))
(17 35 20)

ELISP> (fxyz 1 2 3)
17
ELISP> (fxyz 3 4 5)
35
ELISP> (fxyz 2 3 1)
20
ELISP>

Zip

(defun zip (&rest xss)
  (if (null (car xss))
      '()
    (cons
     (mapcar #'car xss)
     (apply #'zip (mapcar #'cdr xss)))))

ELISP> (zip (list 1 2 3 4) '(a b c d) '(x y z w))
((1 a x)
 (2 b y)
 (3 c z)
 (4 d w))

Zipwith

(defun zipwith (f &rest xss)
  (map-apply f (apply #'zip xss)))

ELISP> (zipwith #'f '(1 2 3) '(4 5 6) '(3 6 8))
(23 40 53)

ELISP> (f 1 4 3)
23

ELISP> (f 2 5 6)
40

ELISP> (f 3 6 8)
53
ELISP>

Foldr

;;           f :: x -> acc -> acc
;; foldr :: (a -> b -> b) -> b -> [a] -> b
;; foldr :: (x -> acc -> acc) -> acc -> [x] -> acc
;; foldr f z []     = z
;; foldr f z (x:xs) = f x (foldr f z xs)
;;
;;  x = (car xss) , xs = (cdr xss)
(defun foldr (f acc xss)
  (if (null xss)
      ;; foldr f z []     = z
      acc
    ;; foldr f z (x:xs) = f x (foldr f z xs)
    (funcall f (car xss)
             (foldr f acc (cdr xss)))))

ELISP> (foldr (lambda (a b) (+ (* 10 b) a)) 0 '(1 2 3 4 5))
54321
ELISP>

ELISP> (foldr #'+ 0 '(1 2 3 4 5))
15
ELISP>

Foldl

;; foldl :: (b -> a -> b) -> b -> [a] -> b
;; foldl f z []     = z
;; foldl f z (x:xs) = foldl f (f z x) xs
(defun foldl (f acc xss)
  (if (null xss)
      acc
    (foldl f (funcall f acc (car xss)) (cdr xss))))

ELISP> (foldl (lambda (a b) (+ (* 10 a) b)) 0 '(1 2 3 4 5))
12345
ELISP>

Map Pairs

(defun map-pair (func xs)
  (mapcar (lambda (x) (cons x (funcall func x))) xs))

ELISP> (map-pair #'1+ '(1 2 3 4))
((1 . 2)
 (2 . 3)
 (3 . 4)
 (4 . 5))

ELISP> (map-pair #'log10 '(1 10 100 1000 10000))
((1 . 0.0)
 (10 . 1.0)
 (100 . 2.0)
 (1000 . 3.0)
 (10000 . 4.0))

(defun buffer-mode (buffer-or-string)
  "Returns the major mode associated with a buffer."
  (with-current-buffer buffer-or-string
    major-mode))

ELISP> (map-pair #'buffer-mode (buffer-list))
((#<buffer *ielm*> . inferior-emacs-lisp-mode)
 (#<buffer *scratch*> . lisp-interaction-mode)
 (#<buffer *Backtrace*> . debugger-mode)
 (#<buffer *GNU Emacs*> . fundamental-mode)
 (#<buffer  *Minibuf-1*> . minibuffer-inactive-mode)
 (#<buffer  *Minibuf-0*> . minibuffer-inactive-mode)
 (#<buffer *Messages*> . messages-buffer-mode)

Map pairs xy

(defun map-xypair (func-x func-y xs)
  (mapcar
   (lambda (x)
     (cons (funcall func-x x) (funcall func-y x)))
   xs))

ELISP> (map-xypair #'buffer-name #'buffer-mode (buffer-list))
(("*ielm*" . inferior-emacs-lisp-mode)
 ("*scratch*" . lisp-interaction-mode)
 ("*Backtrace*" . debugger-mode)
 ("*GNU Emacs*" . fundamental-mode)
 (" *Minibuf-1*" . minibuffer-inactive-mode)
 (" *Minibuf-0*" . minibuffer-inactive-mode)
 ("*Messages*" . messages-buffer-mode)
 (" *code-conversion-work*" . fundamental-mode)
 (" *Echo Area 0*" . fundamental-mode)
 (" *Echo Area 1*" . fundamental-mode)
 (" *http www.httpbin.org:80*" . fundamental-mode)
 (" *http www.httpbin.org:80*-820734" . fundamental-mode)
 (" *http www.httpbin.org:80*-914099" . fundamental-mode)
 (" *http www.httpbin.org:80*-945998" . fundamental-mode)
 ("*Help*" . help-mode)
 ("*Completions*" . completion-list-mode))

Juxt

ELISP> (juxt #'buffer-name #'buffer-mode)
(lambda
  (x)
  (list
   ((funcall #'buffer-name x)
    (funcall #'buffer-mode x))))


ELISP> (funcall (juxt #'buffer-file-name  #'buffer-name #'buffer-mode) (current-buffer))
(nil "*ielm*" inferior-emacs-lisp-mode)

ELISP> (mapcar (juxt #'buffer-name #'buffer-file-name #'buffer-mode) (buffer-list))
(("*ielm*" nil inferior-emacs-lisp-mode)
 ("*scratch*" nil lisp-interaction-mode)
 ("passgen.py" "/home/tux/bin/passgen.py" python-mode)
 (".bashrc" "/home/tux/.bashrc" sh-mode)
 (" *Minibuf-1*" nil minibuffer-inactive-mode)
 ("init.el" "/home/tux/.emacs.d/init.el" emacs-lisp-mode)
 ("*Backtrace*" nil debugger-mode)
 ("*GNU Emacs*" nil fundamental-mode)
 (" *Minibuf-0*" nil minibuffer-inactive-mode)
 ("*Messages*" nil messages-buffer-mode)
 (" *code-conversion-work*" nil fundamental-mode)
 (" *Echo Area 0*" nil fundamental-mode)
 (" *Echo Area 1*" nil fundamental-mode)
 (" *http www.httpbin.org:80*" nil fundamental-mode)
 (" *http www.httpbin.org:80*-820734" nil fundamental-mode)
 (" *http www.httpbin.org:80*-914099" nil fundamental-mode)
 (" *http www.httpbin.org:80*-945998" nil fundamental-mode)
 ("*Help*" nil help-mode)
 ("*Completions*" nil completion-list-mode))

Map Juxt

(defmacro map-juxt (xs_f xs)
  `(mapcar (juxt ,@xs_f) ,xs))


ELISP> (map-juxt (#'buffer-name #'buffer-file-name #'buffer-mode) (buffer-list))
(("*ielm*" nil inferior-emacs-lisp-mode)
 ("*scratch*" nil lisp-interaction-mode)
 ("passgen.py" "/home/tux/bin/passgen.py" python-mode)
 (".bashrc" "/home/tux/.bashrc" sh-mode)
 (" *Minibuf-1*" nil minibuffer-inactive-mode)
 ("init.el" "/home/tux/.emacs.d/init.el" emacs-lisp-mode)
 ("*Backtrace*" nil debugger-mode)
 ("*GNU Emacs*" nil fundamental-mode)
 (" *Minibuf-0*" nil minibuffer-inactive-mode)
 ("*Messages*" nil messages-buffer-mode)
 ...

Lambda Function Macro

(defmacro $f (f &rest params)
  `(lambda ($) (,f ,@params)))


ELISP> ($f - 10 $)
(lambda
  ($)
  (- 10 $))

ELISP> ($f * (+ 3 $) 5)
(lambda
  ($)
  (*
   (+ 3 $)
   5))

ELISP> (funcall ($f * (+ 3 $) 5) 10)
65
ELISP> (mapcar  ($f * (+ 3 $) 5) '(1 2 3 4 5))
(20 25 30 35 40)

ELISP>
ELISP> (mapcar  ($f list (1+ $) (1- $) (log10 $)) '(1 10 100 1000))
((2 0 0.0)
 (11 9 1.0)
 (101 99 2.0)
 (1001 999 3.0))

Partial Application

(defmacro $c (f  &rest params)
  `(lambda (__x) (,f ,@params __x)))

ELISP> (defun f (x y z) (+ (* 3 x) (* 2 y) (* 4 z)))
f
ELISP> (f 1 2 3)
19
ELISP> ($c f 1 2)
(lambda
  (__x)
  (f 1 2 __x))

ELISP> (mapcar ($c f 1 2) '(1 2 3 4 5))
(11 15 19 23 27)

ELISP> (mapcar ($c + 1 2) '(1 2 3 4 5))
(4 5 6 7 8)

ELISP>

2.15 Structures

ELISP> (defstruct account id name balance)
account
ELISP> (make-account :id 3434 :name "John" :balance 1000.34)
[cl-struct-account 3434 "John" 1000.34]

ELISP> (setq user1 (make-account :id 3434 :name "John" :balance 1000.34))
[cl-struct-account 3434 "John" 1000.34]

ELISP> (account-name user1)
"John"

ELISP> (account-id user1)
3434

ELISP> (account-balance user1)
1000.34

;; Test if input is an account object
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
ELISP> (account-p user1)
t
ELISP>

;; Change Field
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (defun withdraw (accc amount)
         (setf (account-balance acc) (- (account-balance acc) amount)))
withdraw

ELISP> (withdraw user1 300)
700.34
ELISP> user1
[cl-struct-account 3434 "John" 700.34]

ELISP> (withdraw user1 500)
200.34000000000003
ELISP> user1
[cl-struct-account 3434 "John" 200.34000000000003]

ELISP>

;; Build structure from a list of parameters
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (defun build-account (id name balance)
         (make-account :id id :name name  :balance balance))
build-account

ELISP> (build-account 3434 "O' Neil" 35434.23)
[cl-struct-account 3434 "O' Neil" 35434.23]

ELISP> (apply 'build-account '(3434 "O' Neil" 35434.23))
[cl-struct-account 3434 "O' Neil" 35434.23]

ELISP>

ELISP> (mapcar (lambda (params) (apply 'build-account params))
               '(
                 (34423 "O' Neil" 23.2323)
                 (1023  "John Edwards" 1002323.23)
                 (92323 "Mr. Dummy"  2323241.2323)
                 (8723  "John Oliver" 9823)
                 ))
([cl-struct-account 34423 "O' Neil" 23.2323]
 [cl-struct-account 1023 "John Edwards" 1002323.23]
 [cl-struct-account 92323 "Mr. Dummy" 2323241.2323]
 [cl-struct-account 8723 "John Oliver" 9823])

ELISP>

ELISP> (defun build-accounts-from-list (list-of-params)
         (mapcar (lambda (params) (apply 'build-account params)) list-of-params))
build-accounts-from-list
ELISP>

ELISP> (setq accounts (build-accounts-from-list
                       '(
                         (34423 "O' Neil" 23.2323)
                         (1023  "John Edwards" 1002323.23)
                         (92323 "Mr. Dummy"  2323241.2323)
                         (8723  "John Oliver" 9823)
                         )))
([cl-struct-account 34423 "O' Neil" 23.2323]
 [cl-struct-account 1023 "John Edwards" 1002323.23]
 [cl-struct-account 92323 "Mr. Dummy" 2323241.2323]
 [cl-struct-account 8723 "John Oliver" 9823])

ELISP> accounts
([cl-struct-account 34423 "O' Neil" 23.2323]
 [cl-struct-account 1023 "John Edwards" 1002323.23]
 [cl-struct-account 92323 "Mr. Dummy" 2323241.2323]
 [cl-struct-account 8723 "John Oliver" 9823])

ELISP> (mapcar #'account-id accounts)
(34423 1023 92323 8723)

ELISP>

ELISP>
ELISP> (mapcar #'account-name accounts)
("O' Neil" "John Edwards" "Mr. Dummy" "John Oliver")

ELISP>


ELISP> (mapcar #'account-balance accounts)
(23.2323 1002323.23 2323241.2323 9823)

ELISP>

3 宏和元编程

3.1 Quasi-quote

;;;; Quasiquote

> `(the product of 3 and 4 is ,(* 3 4))
(the product of 3 and 4 is 12)

> `("the product of 3 and 4 is" ,(* 3 4))
("the product of 3 and 4 is" 12)

> `("the value of (exp 3) is " ,(exp 3) "the value of (sqrt 100) is" ,(sqrt 100))
("the value of (exp 3) is " 20.085536923187668 "the value of (sqrt 100) is" 10.0)

> `(a ,a b ,b c ,c d ,d)
(a 10 b 20 c my-symbol d "a string")

> `((a . ,a) (b . ,b) (c . ,c) (d . ,d))
((a . 10)
 (b . 20)
 (c . my-symbol)
 (d . "a string"))

> (setq xs '(sym1 sym2 sym3))
(sym1 sym2 sym3)

> xs
(sym1 sym2 sym3)

> `(xs ,xs)
(xs
 (sym1 sym2 sym3))

> `(xs ,@xs)
(xs sym1 sym2 sym3)

> `(if (< ,a ,b) ,(+ a 4) ,d)
(if
    (< 10 20)
    14 "a string")

> (eval `(if (< ,a ,b) ,(+ a 4) ,d))
14
>

> (eval `(if (> ,a ,b) ,(+ a 4) ,d))
"a string"

;;------------------

> (setq xlist '(1 2 3 4))
(1 2 3 4)

> (setq ylist '(a b c d e))
(a b c d e)

> `(xs ,xlist ys ,ylist)
(xs
 (1 2 3 4)
 ys
 (a b c d e))

> `(xs ,@xlist ys ,@ylist)
(xs 1 2 3 4 ys a b c d e)

3.2

定义lambda函数语法糖:λ

(defmacro λ (args body)
  `(lambda ,args ,body))

ELISP> (λ (x) (+ x 3))
(lambda
  (x)
  (+ x 3))
ELISP> (mapcar (λ (x) (+ x 3)) '(1 2 3 4 5 6))
(4 5 6 7 8 9)

Set variable to nil

(defmacro nil! (var)
  `(setq ,var nil))

ELISP> (setq x 10)
10
ELISP> x
10
ELISP>

ELISP> (nil! x)
nil
ELISP> x
nil
ELISP>

ELISP> (nil! z)
nil
ELISP> z
nil
ELISP>

Create Clojure def, defn and fn special forms

(defmacro fn (args body)
  `(lambda ,args ,body))

(defmacro def (name value)
  `(setq ,name ,value))

(defmacro defn (name args body)
  `(defun ,name ,args ,body))

ELISP> (fn (x) (* x x))
(lambda
  (x)
  (* x x))

ELISP> (mapcar (fn (x) (* x x)) '(1 2 3 4 5))
(1 4 9 16 25)

ELISP> (def x 1000)
1000
ELISP> x
1000
ELISP>

ELISP> (defn f (x y z) (+ (* 3 x) (* -4 y) (* 5 z)))
f
ELISP> (f 4 5 6)
22
ELISP>

……

4 Emacs API

4.1 Emacs术语

Emacs Terminology Description
Point Cursor position, number of characters from beggining of the buffer to current cursor position.
Buffer Place where the user edit something. Not all buffers are bound to a file.
Mark Beginning of the selected area.
Region Selected area/ text
Frame The current window of emacs
Windows Each frame can be split in sections that Emacs documentation calls windows
Fill Word Wrap
Yank Copy
Kill Region Cut
Kill Ring Clipboard
Kill Buffer Close Buffer
Mode Line Status Bar
Font Locking Syntax Coloring

Ben's Journal: 11 Concepts The Emacs Newbie Should Master

4.2 Emacs API

API对象

  • Buffer
  • Temporary Buffer
  • Modes
  • Mode Hooks
  • Mode Map
  • Window
  • Frame
  • Point
  • Process
  • Network Process
  • Minibuffers

4.3 Buffers

4.3.1 Buffer Attributes

(buffer-list)
(current-buffer)
(mapcar #'buffer-name (buffer-list))
(mapcar #'buffer-file-name (buffer-list))
(kill-buffer "init.el")
(get-buffer "*scratch*")

列出打开文件

(defun opened-files ()
  "list all opened file in current session"
  (interactive)
  (remove-if 'null (mapcar 'buffer-file-name (buffer-list))))

(opened-files)

创建新buffer

;;
;;
;; This function returns a buffer named  buffer-or-name.
;; The buffer returned does not become the current
;; buffer—this function does not change which buffer is current.
;;

ELISP> (get-buffer-create "foobar")
#<buffer foobar>
ELISP>

;;
;;  Divide the screen in two windows, and switch to the new buffer
;;  window
;;
ELISP> (switch-to-buffer-other-window "foobar")
#<buffer foobar>
ELISP>

;; Clean Current Buffer
;;
ELISP> (erase-buffer)
nil
ELISP>

;;  Edit another buffer and go back to the old buffer
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (defun within-buffer (name function)
         (let (curbuff (current-buffer))
           (switch-to-buffer name)
           (funcall function)
           (switch-to-buffer current-buffer)
           ))

ELISP> (within-buffer "foobar" (lambda () (insert "dummy")))
#<buffer *ielm*>
ELISP>
ELISP> (lambda (x)(* x 10))
(lambda
  (x)
  (* x 10))

;;;; Translated from: http://d.hatena.ne.jp/rubikitch/20100201/elispsyntax
;;
ELISP> ;; test-buffer Create a buffer named, to write a variety of content
(with-current-buffer (get-buffer-create "test-buffer")
  ;; Empty the contents of the buffer
  (erase-buffer)
  ;; /tmp/foo.txt Make the contents inserted
  (insert-file-contents "/etc/fstab")
  ;; Insert a string
  (insert "End\n")
  ;; Write the contents of a buffer to a file
  (write-region (point-min) (point-max) "/tmp/bar.txt"))
nil
ELISP>

4.3.2 Buffer Mode

Show Buffers Mode

ELISP> (defun buffer-mode (buffer-or-string)
         "Returns the major mode associated with a buffer."
         (with-current-buffer buffer-or-string
           major-mode))
buffer-mode

ELISP> (mapcar (lambda (b)(
                           let
                           (
                            (name (buffer-name b))
                            (type   (buffer-mode (buffer-name b)))
                            )
                           (list name type)
                           ))
               (buffer-list))
(("*ielm*" inferior-emacs-lisp-mode)
 ("*SPEEDBAR*" speedbar-mode)
 (" *Minibuf-1*" minibuffer-inactive-mode)
 ("*scratch*" emacs-lisp-mode)
 ("test3.ml" tuareg-mode)
 ("*Help*" help-mode)
 ("*Messages*" messages-buffer-mode)
 ("sbet.ml" tuareg-mode)
 (" *Minibuf-0*" minibuffer-inactive-mode)
 ("test.el" emacs-lisp-mode)
 ...

4.3.3 Get Buffer Contents / Selection / Line

Get Buffer Content as String

ELISP> (defun buffer-content (name)
         (with-current-buffer name
           (buffer-substring-no-properties (point-min) (point-max))))
buffer-content
ELISP>

ELISP> (buffer-content "test3.ml")
"\n\nlet rec prodlist = function \n    | [] ... "

Get Selected text in current buffer as string

(defun get-selection ()
  "Get the text selected in current buffer as string"
  (interactive)
  (buffer-substring-no-properties (region-beginning) (region-end))
  )

Get current line in current buffer

(defun get-current-line ()
  (interactive)
  "Get current line, where the cursor lies in the current buffer"
  (replace-regexp-in-string "[\n|\s\t]+$" "" (thing-at-point 'line t))
  )

4.3.4 Search and Replace in the entire Buffer

(defun replace-regexp-entire-buffer (pattern replacement)
  "Perform regular-expression replacement throughout buffer."
  (interactive
   (let ((args (query-replace-read-args "Replace" t)))
     (setcdr (cdr args) nil)    ; remove third value returned from query---args
     args))
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward pattern nil t)
      (replace-match replacement))))

4.4 Point, Region, Line and Buffer

4.4.1 Point

Point

Function Description
(point) Current cursor position
(point-min) Minimum cursor position in current buffer. (always returns 1)
(point-max) Maximum cursor position in current buffer.
   
(line-beginning-position) Point of the beginning of current line.
(line-end-position) Point of the end of current line.
   
(region-beginning) Position of the beginning current region (selected text).
(region-end) Position of the end current region.
   
(bounds-of-thing-at-point <thing>) Returns the cons pair '(beginning . end) position of thing at point.

Point Interface Functions

Function Description
(goto-char <point>) Move the cursor to a given point.
(insert <string>) Insert text at current point.
(buffer-substring [pmin] [pmax]) Returns the text with properties between the points <pmin> and <pmax>.
(buffer-substring-no-properties [pmin] pmax]) Returns the text without properties between the points.
(delete-region [pmin] [pmax]) Deletes the text between <pmin> and <pmax>.
> (point)
99696

> (point-min)
1


> (point-max)
185623

>  (line-beginning-position)
99774

>  (line-end-position)
99804

> (buffer-substring-no-properties
   (line-beginning-position)
   (line-end-position))

(defun delete-line ()
  (interactive)
  (delete-region  (line-beginning-position)  (line-end-position)))

(defun delete-region ()
  (interactive)
  (delete-region  (region-beginning) (region-end)))

(defun insert-end-of-buffer ()
  (interactive)

  ;; Save Current Cursor Position
  ;; and go back to initial positon when
  ;; finish this block
  (save-excursion
    (goto-char (point-max)) ;;; Go to end of buffer
    (insert "Testing insert end of buffer")
    ))

4.4.2 Thing at Point API

???

4.5 Message / Output

(message "Hello world")
(message-box "Time for a break.\nDrink some coffee")

4.6 Files, Directories and Path

4.6.1 Basic Functions

;; Get and Set current directory

ELISP> (pwd)
"Directory /home/tux/tmp/"

ELISP> (cd "/etc/")
"/etc/"

ELISP> (pwd)
"Directory /etc/"
ELISP>


ELISP> (file-name-directory "/etc/hosts")
"/etc/"

;; Expand File Name
;;
ELISP> (expand-file-name "~/")
"/home/tux/"
ELISP> (expand-file-name ".")
"/home/tux/tmp"
ELISP> (expand-file-name "..")
"/home/tux"
ELISP>


;;;;; Create a Directory
;;;
ELISP> (mkdir "dummy")
nil
ELISP> (mkdir "dummy")
** Eval error **  File exists: /home/tux/dummy
ELISP>

;;; List Directory
;;;;
;;;
ELISP> (directory-files "/home/tux/PycharmProjects/Haskell/")
("." ".." ".git" ".gitignore" ".idea" "LICENSE" "Make" "Makefile"
 "README.back.md" "README.html" "README.md" "Test.html" "build.sh" "clean.sh"
 "codes" "dict.sh" "haskell" "ocaml" "papers" "tags" "tmp")

4.6.2 File Name Components

ELISP> (file-name-directory "/usr/bin/env")
"/usr/bin/"
ELISP>

ELISP> (file-name-nondirectory "/usr/bin/env")
"env"
ELISP>


ELISP> (file-name-base "/home/foo/zoo1.c")
"zoo1"
ELISP> (file-name-base "/home/foo/zoo1.c.back")
"zoo1.c"

4.6.3 Read / Write file to a string

Read File

ELISP> (defun file-contents (filename)
  (interactive "fFind file: ")
  (with-temp-buffer
    (insert-file-contents filename) ;; 先将文件内容插入临时buffer,再读取内容
    (buffer-substring-no-properties (point-min) (point-max))))

ELISP> (file-contents "/proc/filesystems")
"nodev  sysfs\nnodev    rootfs\nnodev   ramfs\nnodev
bdev\nnodev proc\nnodev cgroup\nnode ...

Write to File

ELISP> (append-to-file "hello world" nil "/tmp/hello.txt")
nil

ELISP> (file-contents "/tmp/hello.txt")
"hello world"
ELISP>

4.7 Window Functions

4.7.1 Basic Window Functions

(split-window-horizontally)
(split-window-vertically)
(delete-other-windows)
(switch-to-buffer-other-window "init.el")
(delete-window)
(make-frame)
(frame-list)
(delete-frame)

4.7.3 Window Configuration

(current-window-configuration)
(setq w (current-window-configuration))
w
(set-window-configuration w)
;; Screen Resolution

ELISP> (x-display-pixel-width)
1366

ELISP> (x-display-pixel-height)
768
ELISP>
ELISP>

;; Resize and Set Emacs Windows position
;;
;; From: http://uce.uniovi.es/tips/Emacs/mydotemacs.html#sec-41
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

ELISP> (defun resize-frame ()
  "Set size"
  (interactive)
  (set-frame-width (selected-frame) 100)
  (set-frame-height (selected-frame) 28)
  (set-frame-position (selected-frame) 0 1))
resize-frame
ELISP>

ELISP> (resize-frame)
t
ELISP>

4.8 OS Interface

4.8.1 Find the current operating system

Value Description
gnu GNU Hurd system.
gnu/linux GNU/Linux system.
gnu/kfreebsd GNU system with a FreeBSD kernel.
darwin Darwin (GNU-Darwin, Mac OS X)
ms-dos MS-DOS application.
windows-nt native W32 application.
cygwin compiled using the Cygwin library
system-type
system-configuration

4.8.2 Date and Time

(current-time)
(insert (format-time-string "%Y-%m-%d")) ;; 2019-12-15
(insert (format-time-string "%H:%M:%S")) ;; 16:11:04
(format-time-string "%d/%m/%Y %H:%M:%S" (current-time))

4.8.3 Call External Commands or Apps

(call-process "mpd")
(shell-command-to-string "pwd")

4.8.4 Environment Variables

(getenv "PATH")
(split-string (getenv "PATH") ":")
(dolist (e (split-string  (getenv "PATH") ":")) (princ (format "%s\n" e)))
exec-path
(getenv "HOME")
(setenv "JAVA_HOME" "/usr/local/java")

system-type
(eq system-type 'gnu/linux)()
(dolist (e process-environment) (princ (format "%s\n" e)))

4.8.5 Process Management

(process-list)
(get-process "merlin")
(mapcar 'process-name (process-list))

;;;; Buffer Process
(process-command (get-process "vterm"))
(process-id (get-process "vterm"))
(process-buffer (get-process "vterm"))
(buffer-name (process-buffer (get-process "vterm")))
(mapcar (lambda (p) (buffer-name (process-buffer p))) (process-list))
(display-buffer (process-buffer (get-process "vterm")))

;;;; Start Asyncronous Process
;;  Start the process named py, with the buffer named pybff
;;  using the command python, /usr/bin/python (on linux)
(start-process "py" "pybff" "python")
;; End the process named py
(process-send-eof "py")

(process-send-string "py" "print 'Hello world'\n")

;;;; Get Multiple Fields

(mapcar
 (lambda (p)(list
             p
             (process-name p)
             (process-command p)
             (list (process-buffer p) (buffer-name (process-buffer p)))
             (process-id p)
             (process-status p)
             ))
 (process-list))

4.9 Interfaces

4.9.1 Creating Quick Access Menu

(require 'easymenu)

(easy-menu-define djcb-menu global-map "Utils"
  '("Utils"
    ("Shells" ;; submenu
     ["Ielm   - Emacs Lisp Shell"       (ielm)]
     ["Eshell - Emacs Buitin Shell"    (eshell)]
     ["Native Shell "                  (shell)]
     ["---------------------" nil]
     ["Edit .bashrc" (find-file  "~/.bashrc")]
     ["Edit .profile" (find-file "~/.profile")]
     ["Edit .Xresources" (find-file "~/.Xresources")]
     ["Edit .xsessin"    (find-file "~/.xsession")]
     ["See all GNU MAN pages" ( info)]
     ["See a specific Man Page" (woman)]

     );; End of shells menu

    ("Emacs /Elisp"  ;; submenu

     ["Ielm   - Emacs Lisp Shell"  (ielm)]
     ["Eval buffer"   (eval-buffer) ]
     ["---------------------" nil]

     ["Edit  init.el" (find-file  user-init-file)]
     ["Reload init.el" (load-file user-init-file)]
     ["Open .emac.d dir" (find-file "~/.emacs.d")]
     ["List packages"     (list-packages)]
     ["Install package"   (package-install)]

     ) ;; End of Emacs / Elisp submenu
    )) ;; End of Custom Menu

4.10 Timer

4.10.1 run-with-timer

;;; (run-with-timer SECS REPEAT FUNCTION &rest ARGS)
(run-with-timer 5 nil
                (lambda () (message-box "happy hacking emacs!")))

(defun cofee-wait ()
  (interactive)
  (let ((minutes 3))
    (run-with-timer (* 60 minutes)  nil
                    (lambda () (message-box "Coffee done"))
                    )
    (message "Waiting for the cofee")
    ))

4.11 Emacs Modes

4.11.1 Mode Association with Files

;; 列出所有和拓展名相关的mode
auto-mode-alist
;; 列出与一个mode相关的所有拓展名
(remove-if-not
 (lambda (al) (equal (cdr al) 'web-mode)) auto-mode-alist)
;; 为一个mode关联拓展名
(add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))

4.11.2 Lisp Routines to introspect modes??

(defun show-doc (function)
  (princ (documentation function)))

(defun mode/show ()
  "  Returns all modes associated with files

     To query the file extesions associated with a mode
     use:
         > (mode/ftypes 'markdown-mode)

     for example.
  "
  (dolist (m (remove-if #'listp
                        (mapcar #'cdr auto-mode-alist))) (print m)))

(defun mode/ftypes (mode)
  "
  Get all file extension associated with a mode.

  Usage:

  ELISP> (get-mode-ftypes 'markdown-mode)
  ((\"\\.md\\'\" . markdown-mode)
  (\"\\.text\\'\" . markdown-mode)
  (\"\\.markdown\\'\" . markdown-mode)

  "
  (remove-if-not
   (lambda (al)
     (equal (cdr al) mode))
   auto-mode-alist))

ELISP> (mode/ftypes 'clojure-mode)
(("\\(?:build\\|profile\\)\\.boot\\'" . clojure-mode)
 ("\\.\\(clj\\|dtm\\|edn\\)\\'" . clojure-mode))

ELISP> (mode/ftypes 'scheme-mode)
(("\\.\\(scm\\|stk\\|ss\\|sch\\)\\'" . scheme-mode)
 ("\\.scm\\.[0-9]*\\'" . scheme-mode)
 ("\\.oak\\'" . scheme-mode))

ELISP> (show-doc #'mode/ftypes)

Get all file extension associated with a mode.

Usage:

ELISP> (get-mode-ftypes 'markdown-mode)
(("\.md\'" . markdown-mode)
 ("\.text\'" . markdown-mode)
 ("\.markdown\'" . markdown-mode))

4.11.3 Mode Specific Key Bindings

(define-key emacs-lisp-mode-map (kbd "<f5>")
  (lambda () (interactive) (message "Hello world")))
(add-hook 'lisp-interaction-mode-hook 'turn-on-eldoc-mode)

4.12 Special Variables

emacs-major-version
load-path
window-system
system-type
system-configuration
shell-file-name
user-full-name
user-mail-address
user-init-file
user-emacs-directory
exec-directory

5 正则表达式

5.1 Emacs Regex

Special characters

. any character (but newline)
* previous character or group, repeated 0 or more time
+ previous character or group, repeated 1 or more time
? previous character or group, repeated 0 or 1 time
^ start of line
$ end of line
[…] any character between brackets
[^..] any character not in the brackets
[a-z] any character between a and z
\ prevents interpretation of following special char
\ or
\w word constituent
\b word boundary
\sc character with c syntax (e.g. \s- for whitespace char)
  start\end of group
\< \> start\end of word
\` \' start\end of buffer
\1 string matched by the first group
\n string matched by the nth group
\{3\} previous character or group, repeated 3 times
\{3,\} previous character or group, repeated 3 or more times
\{3,6\} previous character or group, repeated 3 to 6 times

POSIX Character classes

[:digit:] digit, same as [0-9]
[:upper:] letter in uppercase
[:space:] whitespace character, as defined by the syntax table
[:xdigit:] hexadecimal digit
[:cntrl:] control character
[:ascii:] ascii character

Syntax Classes

\s- whitespace character \s/ character quote character
\sw word constituent \s$ paired delimiter
\s_ symbol constituent \s' expression prefix
\s. punctuation character \s< comment starter
\s( open delimiter character \s> comment starter
\s) close delimiter character \s! generic comment delimiter
\s" string quote character \s generic string delimiter
\s\ escape character    

Emacs X Perl Regex

Emacs Regex Perl Regex Description
  ( ) Capture group
\{ \} { }  
\s- \s White space
\1, \2, \3, \4 $1, $2, $3 Result of capture: search, replace.
[ ] [ ] Character class
[0-9] or [:digit:] \d Digit from 0 to 9
\b \b Word boundary
\w \w Word character

5.2 Regex Commands

C-M-s incremental forward search matching regexp
C-M-r incremental backward search matching regexp

Buffer Commands

M-x replace-regexp replace string matching regexp
M-x query-replace-regexp same, but query before each replacement
M-x align-regexp align, using strings matching regexp as delimiters
M-x highlight-regexp highlight strings matching regexp
M-x grep call unix grep command and put result in a buffer
M-x lgrep user-friendly interface to the grep command
M-x rgrep recursive grep
M-x dired-do-copy-regexp copy files with names matching regexp
M-x dired-do-rename-regexp rename files matching regexp
M-x find-grep-dired display files containing matches for regexp with Dired

Line Commands

Command (M-x command) Alias Description
keep-lines delete-non-matching-lines Delete all lines except those containing matches
flush-lines delete-matching-lines Delete lines containing matches
highlight-lines-matching-regexp hi-lock-line-face-buffer Highlight lines matching regexp
occur list-matching-lines Show lines containing a match
multi-occur   Show lines in all buffers containing a match
how-many count-matches Count the number of strings matching regexp

5.3 Regex Functions

5.3.1 match-string

5.3.2 match-end

5.3.3 match-beginning

5.3.4 re-search

5.3.5 re-search-forward

5.3.6 replace-string-in-regexp

5.3.7 replace-string

5.4 Build regex interactively

M-x re-builder

M-x query-replace-regexp

5.5 Emacs Regex rx-notation

(require 'rx)

;;  (rx <patterns>)

ELISP> (rx digit)
"[[:digit:]]"

ELISP> (rx-to-string '(or "foo" "bar"))
"\\(?:\\(?:bar\\|foo\\)\\)"
Description rx notation Emacs regex
Beginning of Line bol ^
End of Line eol $
     
Begining of String bos \\`
End of String eos \\'
     
Beginning of Word bow \\<
End of Word eow \\>
     
Digit 0 to 9 digit \lbr\lbr:digit:\rbr\rbr
Hexadecimal digit hex \lbr\lbr:xdigit:\rbr\rbr
Match ascii character    
Match anything lower case lower \lbr\lbr:lower:\rbr\rbr
Match anything upper case upper \lbr\lbr:upper:\rbr\rbr
     
word   \sw

example

ELISP> (require 'rx)
rx

ELISP> (rx (+ digit))
"[[:digit:]]+"

ELISP> (rx digit (+ digit))
"[[:digit:]][[:digit:]]+"

ELISP> (rx bol (+ digit) eol)
"^[[:digit:]]+$"

ELISP> (rx (zero-or-more digit))
"[[:digit:]]*"

ELISP> (rx (one-or-more digit))
"[[:digit:]]+"

ELISP> (rx (or "cat" "rat" "dog"))
"\\(?:cat\\|dog\\|rat\\)"

;; (replace-regexp-in-string REGEXP REP STRING
;;      &optional FIXEDCASE LITERAL SUBEXP START)

ELISP> (replace-regexp-in-string
          (rx (or "cat" "rat" "dog"))
          ""
          "cat cata rat rat dograt dog cat2334 23rat2")
" a     2334 232"

;; Replaces only in the beggining of line
;;
ELISP>  (replace-regexp-in-string
          (rx bol (or "cat" "rat" "dog"))
          ""
          "cat cata rat rat dograt dog cat2334 23rat2")
" cata rat rat dograt dog cat2334 23rat2"


ELISP>  (replace-regexp-in-string
          (rx bow (or "cat" "rat" "dog") eow)
          ""
          "cat cata rat rat dograt dog cat2334 23rat2")
" cata   dograt  cat2334 23rat2"

ELISP>  (rx bow (or "cat" "rat" "dog") eow)
"\\<\\(?:cat\\|dog\\|rat\\)\\>"

;;  Removes all whitespaces
;;
ELISP>  (replace-regexp-in-string
          (rx (* whitespace))
          ""
          "cat cata rat rat dograt dog cat2334 23rat2")

"catcataratratdogratdogcat233423rat"

ELISP>  (replace-regexp-in-string
          (rx (* whitespace))
          ""
          "cat cata rat rat dograt dog cat2334 23rat2")

"catcataratratdogratdogcat233423rat"

;; Capture group
;;
ELISP>  (replace-regexp-in-string
          (rx (submatch bow (or "cat" "rat" "dog") eow))
          "(\\1)"
          "cat cata rat rat dograt dog cat2334 23rat2")

"(cat) cata (rat) (rat) dograt (dog) cat2334 23rat2"

ELISP> (rx (submatch bow (or "cat" "rat" "dog") eow))
"\\(\\<\\(?:cat\\|dog\\|rat\\)\\>\\)"
发布于 2019-11-12