Skip to content

Latest commit

 

History

History
1190 lines (915 loc) · 24.8 KB

random.org

File metadata and controls

1190 lines (915 loc) · 24.8 KB

Table of Contents

defsubst

使用 defsubst 定义 Inline Function —— 像 Macro 的 Function。

(defsubst foo () (message "Foo is an inline funciton"))

经过 Byte-compile,调用 Inline Function 的地方会像 Macro 那样直接插入 Function Body,从而避免函数调用的负担。

(defun bar () (foo))
(byte-compile 'bar)
(symbol-function 'bar)

1 + 2 + 3 + … + 100

Bad (waste space)

(apply #'+ (number-sequence 1 100))

Bad

(cl-reduce #'+ (number-sequence 1 100))

Best

(cl-loop for i from 1 to 100
         sum i)

Good

(let ((i 1)
      (sum 0))
  (while (<= i 100)
    (setq sum (+ sum i))
    (setq i (1+ i)))
  sum)

Good

(require 'stream)
(seq-reduce #'+ (stream-range 1 101) 0)

'(1 2 3)(list 1 2 3)

这两种写法有本质不同,有时能交换使用,有时又不能。选中那种写法首先考虑对不对,然后在考虑风格。

  • `(,1 2 3)(list 1 2 3)
  • `(,1 ,2 3)(list 1 2 3)
  • `(,1 ,2 ,3) = (list 1 2 3)

bar 一般属于错误:

(defun foo () (list 1 2 3))
(defun bar () '(1 2 3))
(cl-incf (car (bar)) 100)
(cl-incf (car (bar)) 100)
(symbol-function 'bar)

cl-loop

append vs collect

(cl-loop for k in '(1 2 3)
         for v in '(a b c)
         collect (list k v))
(cl-loop for k in '(1 2 3)
         for v in '(a b c)
         append (list k v))

in-ref vs in

(let ((nums (list 1 2 3)))
  (cl-loop for i in nums
           do (cl-incf i))
  nums)
(let ((nums (list 1 2 3)))
  (cl-loop for i in-ref nums
           do (cl-incf i))
  nums)

into

(cl-loop for i from 1 to 100
         sum i into result
         finally return (format "1 + 2 + 3 ... + 100 = %s" result))

nconc vs append

(let ((l (list (list 1) (list 2) (list 3))))
  (cl-loop for i in l
           append i)
  l)
(let ((l (list (list 1) (list 2) (list 3))))
  (cl-loop for i in l
           nconc i)
  l)

return

(cl-loop for i from 1
         when (> i 100)
         return "look ma, not catch/throw")

if

(cl-loop for i from 1 to 10
         if (cl-evenp i)
         collect i into evens
         else
         collect i into odds
         finally return (list odds evens))

by

(cl-loop for (a b) on '(1 2 3 4 5 6) by #'cddr
         collect (cons a b))

across-ref

;; 2 维数组
(cl-loop with vv = (make-vector 4 nil)
         for v across-ref vv
         do (setq v (make-vector 3 0))
         finally return vv)

vconcat

(cl-loop for i from 1 to 3
         vconcat (list i))
;; 2 维数组
(cl-loop repeat 4
         vconcat (vector (make-vector 3 0)))

nest cl-loop

(let ((vv [[1 2 3]
           [4 5 6]
           [7 8 9]]))
  (cl-loop for v across vv
           sum (cl-loop for i across v
                        sum i)))
(let ((vv [[1 2 3]
           [4 5 6]
           [7 8 9]]))
  (cl-loop for v across vv
           append (cl-loop for i across v
                           collect i)))

Byte-compiling cl-loop is important

(defun foo ()
  (cl-loop for i below 10000
           do (cl-loop for j below 10000)))

(benchmark-run 1 (foo))
(defun bar ()
  (cl-loop for i below 10000
           do (cl-loop for j below 10000)))

(byte-compile 'bar)
(benchmark-run 1 (bar))

parsec

(parsec-with-input "/* hello */"
  (parsec-string "/*")
  (parsec-many-till-as-string (parsec-any-ch)
                              (parsec-try
                               (parsec-string "*/"))))

How does Emacs Lisp Completion work?

How does Emacs do completion?

(with-temp-buffer
  (emacs-lisp-mode)
  completion-at-point-functions)

Global Functions & Variables

(all-completions "emacs-lisp" obarray #'functionp)

Local variables

(with-temp-buffer
  (emacs-lisp-mode)
  (insert "(let ((foo 1) (bar 2)) (+ foo ))")
  (backward-char 2)
  (elisp--local-variables))
(elisp--local-variables-1 nil '(let ((foo 1) (bar 2)) (+ foo elisp--witness--lisp)))
(elisp--local-variables-1
 nil
 '(let ((foo 1))
    (let ((bar 2))
      (let ((baz 3))
        (+ foo elisp--witness--lisp)))))
(pcase '(let ((foo 1) (bar 2)) (+ foo elisp--witness--lisp))
  (`(let ,bindings . ,body) (mapcar #'car bindings)))

2^0 + 2^1 + 2^2 + … + 2^n = 2^(n+1) - 1

等比数列求和

(cl-loop for i from 0 to 10
         sum (expt 2 i))
(1- (expt 2 11))

\begin{equation} S_n = \frac{a(q^n - 1)}{q - 1} \end{equation}

(defun geometric-series-sum (a q n)
  "Return a*q^0 + a*q^1 + a*q^2 + ... + a*q^1."
  (/ (* a (- (expt q n) 1))
     (- q 1)))

(geometric-series-sum 1 2 11)

10^n % 7

(cl-loop for n from 0 to 17
         collect (% (expt 10 n) 7))
(defun 10^n%7 (n)
  "Return 10^n % 7."
  (nth (% n 6) '(1 3 2 6 4 5)))

(10^n%7 100)

7^n 的个位数

(cl-loop for n from 0 to 20
         collect (% (expt 7 n) 10))
(defun 7^n-digit (n)
  (nth (% n 4) '(1 7 9 3)))

(7^n-digit 987654321)

How to extend pcase?

use pcase-defmacro

(pcase-defmacro my-alist (&rest patterns)
  (cl-loop for pat in patterns
           collect `(app (alist-get ',pat) ,pat) into aux
           finally return `(and ,@aux)))

(pcase '((a . 1) (b . 2) (c . 3) (d . 4))
  ((my-alist a b c d) (list a b c d)))

S(n) = a_1 + a_2 + … + a_n

(defun my-sum (a k n)
  "S(n) = a_1 + a_2 + ... + a_n.
a_i+1 - a_i = k."
  (cl-loop for i from a by k
           repeat n
           sum i))

(defun my-better-sum (a k n)
  (/ (* (+ a (+ a (* (- n 1) k))) n) 2))

(list (my-sum 1 1 100)
      (my-sum 1 2 50)
      (my-better-sum 1 1 100)
      (my-better-sum 1 2 50))

variable vs function | Symbol

(defun my-eval (expr)
  (pcase expr
    (`((lambda . ,_) . ,_) (apply (car expr) (cdr expr)))
    (`(,(and (pred symbolp)
             (pred symbol-function)
             (app symbol-function function))
       . ,args)
     (my-eval (cons function args)))))

Delete first element of list

(let ((l (list 1 2 3 4 5)))
  (setcar l (cadr l))
  (setcdr l (cddr l))
  l)
(defun delete-first (l)
  (cl-assert (> (length l) 1))
  (setcar l (cadr l))
  (setcdr l (cddr l)))

(let ((l (list 1 2 3 4 5)))
  (delete-first l)
  l)

Transient

(define-infix-argument my-grep:--regexp ()
  :description "PATTERN"
  :class 'transient-option
  :key "-e"
  :argument "--regexp=")

(define-transient-command foo ()
  "Foo."
  ["Arguments"
   (my-grep:--regexp)
   ("-i" "Ignore Case" "--ignore-case")]
  ["Do"
   ("g" "grep" foo-grep)])

(defun foo-grep ()
  (interactive)
  (message "[DEBUG] %S" (transient-args 'foo)))

Deal with Interactive from in Advice

(defun greeting (name age)
  (interactive "sName: \nnAge: ")
  (message "Hi, I'm %s, %d years old!" name age))

如果 FUNCTION 没有 Interactive Form,那么会继承旧的 Interactive Form:

(define-advice greeting (:around (old-fun name age) foo)
  (message "Hello, my name is %s" name))

如果 FUNCTION 有 Interactive Form,那么会覆盖旧的 Interactive Form:

(define-advice greeting (:around (old-fun &rest args) foo)
  (interactive "sWhat's your name? \nnHow old are you? ")
  (apply old-fun args))

如果 Function 的 Interactive Form 是个函数,则传进旧的 Interactive Spec:

(define-advice greeting (:around (old-fun &rest args) foo)
  (interactive (lambda (spec)
                 (message "[DEBUG] %s" spec)
                 (message nil)
                 (setq spec (replace-regexp-in-string "Name:" "What's your name?" spec t t))
                 (advice-eval-interactive-spec spec)))
  (apply old-fun args))

(info “(elisp) Programmed Completion”)

Hit ? minibuffer-completion-help to list all possible completions

(completing-read
 "Grep init.el: "
 (lambda (string pred action)
   (pcase action
     ('t (process-lines grep-program "-F" string user-init-file))
     ('nil (car (process-lines grep-program "-F" string user-init-file))))))

测试 Face

(defun foo ()
  (interactive)
  (message (propertize "hello" 'face '(:foreground "red" :weight bold)))
  (message (propertize "world" 'face '(:slant oblique)))
  (message (propertize "world" 'face '(:underline (:color "red" :style wave))))
  (message (propertize "box" 'face '(:box t)))
  (message (propertize "inverse video" 'face '(:inverse-video t))))

(defun face-test-slant ()
  (interactive)
  (with-current-buffer (get-buffer-create "*Test*")
    (erase-buffer)
    (insert
     (mapconcat
      (lambda (sym)
        (propertize (symbol-name sym) 'face (list :slant sym)))
      '(italic
        oblique
        normal
        reverse-italic
        reverse-oblique)
      ", "))
    (goto-char (point-min))
    (display-buffer (current-buffer))))

pip 进度条

Collecting curtsies>=0.1.18 (from bpython)
  Downloading https://files.pythonhosted.org/packages/78/1e/3b69f26d9e496901e80fc90e39e479c85fb6df595c2e2935a4fd781b3c9b/curtsies-0.3.0.tar.gz (47kB)
     |████████████████████████████████| 51kB 1.1MB/s
(defun test ()
  "Python pip progress bar."
  (interactive)
  (dotimes (i 33)
    (message
     (concat "|"
             (make-string i ?█)
             (make-string (- 32 i) ?\s)
             "|"))
    (sit-for .03)))

Emacs Server

两种启动方式

启动方法形式
M-x server-mode前台
emacs –daemon后台

Emacs daemon 就是在后台运行的程序,和 Emacs server 是两个不相干的概念。=emacs –daemon= 会自动开启 Emacs server(不然这个 daemon 不就用不来吗?)

Emacs daemon 和 Emacs server 没关系

为了演示 Emacs daemon 和 Emacs server 的「不相干」,下面把 Emacs daemon 的 Emacs server 关掉

~ $ ~/bin/emacs-25.3 -Q --daemon
Starting Emacs daemon.
~ $ ~/bin/emacsclient --eval 'emacs-version'
"25.3.1"
~ $ ./bin/emacsclient --eval '(server-mode -1)'
~ $ ./bin/emacsclient --eval 'emacs-version'
./bin/emacsclient: can't find socket; have you started the server?
To start the server in Emacs, type "M-x server-start".
./bin/emacsclient: No socket or alternate editor.  Please use:

	--socket-name
	--server-file      (or environment variable EMACS_SERVER_FILE)
	--alternate-editor (or environment variable ALTERNATE_EDITOR)
~ $ ps aux | grep emacs
xcy              67329   0.0  3.9  6347984 161692   ??  S     3:07PM   2:48.78 /Users/xcy/src/emacs-mac/mac/Emacs.app/Contents/MacOS/Emacs
xcy              68173   0.0  0.0  4277256    820 s000  S+    3:58PM   0:00.00 grep --color=auto emacs
xcy              68132   0.0  0.2  4342228   8936   ??  Ss    3:57PM   0:00.16 bin/emacs-25.3 -Q --daemon
~ $ kill 68132
~ $
(expand-file-name server-name server-socket-dir)

Emacs server 的非标准用法

Emacs server 的官方客户端是 emacsclient 。Emacs server 默认用一个 unix domain socket (TCP 是支持的)实现 server-client 交流,这个 socket 文件默认位于

(expand-file-name server-name server-socket-dir)

通信格式约定

通过 C-h f server-process-filter 了解到;

服务器端接受:

-auth AUTH-STRING(TCP) 认证
-env NAME=VALUE环境变量
-dir DIRNAME工作目录
-nowait断开链接
-eval EXPR执行一个 Lisp 表达式
-position LINE[:COLUMN]文件位置
-file FILENAME文件

注意 -eval EXPR 指令中的 EXPR 需要用 server-quote-arg 处理

(server-quote-arg "(+ 1 2)")

客户端接受:

-emacs-pid PIDEmacs process
-window-system-unsupported不支持图形界面
-print STRING打印 -eval 结果
-print-nonl STRING继续 -print

实现客户端

Shell (via netcat)

~ $ nc -U /var/folders/7f/s191h4q97p90374yw15ssrs00000gn/T/emacs501/server
-eval (+&_1&_2)
-emacs-pid 67329
-print 3

上面的 (+&_1&_2) 表示 (+ 1 2)

(server-unquote-arg "(+&_1&_2)")

Emacs Lisp

使用现成的 server-eval-at

(server-eval-at server-name '(+ 1 2))
;; => 3

当然也可以用 the hard way。Emacs Lisp 也是 “batteries included” 的?=make-network-process= 支持 TCP, UDP, unix domain socket 协议,可作 Server 和 Client。

;; Client of Emacs Server via unix domain socket
(let ((buffer "*server-client-test*"))
  (make-network-process
   :service (expand-file-name server-name server-socket-dir)
   :family 'local
   :name "server-client-test"
   :buffer buffer)
  (with-current-buffer buffer
    (process-send-string
     nil
     (concat "-eval "
             (server-quote-arg (format "%S" '(+ 1 2)))
             "\n"))
    (accept-process-output)
    (buffer-string)))

-print 3 表示结果 3

Go

package main

import (
	"fmt"
	"io"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("unix", "/var/folders/7f/s191h4q97p90374yw15ssrs00000gn/T/emacs501/server")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer conn.Close()
	conn.Write([]byte("-eval (+&_1&_2)\n"))
	io.Copy(os.Stdout, conn)
}

什么情况,go run . 没问题,Org Babel with ob-go 会卡死

「实时」JQ 输出

需求:在编辑 JQ Query 同时显示输出

思路:post-command-hook + asynchronous process

实现

输入:获得 JSON

以 Region 的内容作为输入

(buffer-substring-no-properties (region-beginning) (region-end))

不过应该可以直接用 process-send-region

完整

;; 需要开启 lexical-binding
(defun chunyang-jq-live (json)
  (interactive (list (buffer-substring-no-properties
                      (region-beginning)
                      (region-end))))
  (let* ((buf (get-buffer-create "*jq output*"))
         (proc nil)
         (last "")
         (fun (lambda ()
                (let ((query (string-trim (minibuffer-contents-no-properties))))
                  (when (and (not (string= query ""))
                             (not (string= query last)))
                    (when (and proc (process-live-p proc))
                      (kill-process proc))
                    (with-current-buffer buf
                      (erase-buffer))
                    (setq last query)
                    (setq proc
                          (make-process
                           :name "jq"
                           :buffer buf
                           :command (list "jq" query)
                           :connection-type 'pipe
                           :sentinel (lambda (proc event)
                                       (set-window-point
                                        (get-buffer-window (process-buffer proc))
                                        (point-min)))))
                    (process-send-string proc (concat json "\n"))
                    (process-send-eof proc))))))
    (display-buffer buf)
    (minibuffer-with-setup-hook
        (lambda () (add-hook 'post-command-hook fun t t))
      (read-from-minibuffer "jq: "))))

HTTP Range | 断点续传

如果想下载文档一个部分,可以用 HTTP Range Header。有些服务器支持,比如

curl -I example.com | tr -d "\r"

返回的 Accept-Ranges: bytes 就表示这个服务器支持 Range。

curl

下面的 curl 命令会发送 “Range: bytes=-100”

curl -r -100 example.com

Emacs Lisp

(let ((url-request-extra-headers '(("Range" . "bytes=-100"))))
  (display-buffer
   (url-retrieve-synchronously "http://example.com")))

HTTP proxy via HTTP CONNECT

127.0.0.1:1087

> CONNECT example.com:443 HTTP/1.1
> Host: example.com:443
> User-Agent: curl/7.54.0
> Proxy-Connection: Keep-Alive

(setq
 proc
 (make-network-process
  :name "https over http connect"
  :buffer "*https proxy*"
  :host "127.0.0.1"
  :service 1087))
;; => #<process https over http connect>

(process-send-string
 proc
 "GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n")
;; => nil

(process-send-string
 proc
 "CONNECT example.com:443 HTTP/1.1\r\nHost: example.com\r\nProxy-Connection: Keep-Alive\r\n\r\n")
;; => nil

(gnutls-negotiate
 :process proc
 :hostname "example.com"
 :verify-error nil)
;; => #<process https over http connect>

(nsm-verify-connection
 proc
 "example.com"
 443)
;; => #<process https over http connect>

(process-send-string
 proc
 "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: Close\r\n\r\n")
;; => nil

dns lookup

(make-network-process
 :name "dns-lookup"
 :buffer "*dns-lookup*"
 :host "114.114.114.114"
 :service 53
 :type 'datagram
 :coding 'binary)
;; => #<process dns-lookup>

(setq dns-header-spec
      '((id u16)
        (flag bits 2)
        (n1 u16)
        (n2 u16)
        (n3 u16)
        (n4 u16)))
;; => ((id u16) (flag bits 2) (n1 u16) (n2 u16) (n3 u16) (n4 u16))

(vconcat
 (bindat-pack dns-header-spec
              '((id . 1)
                (flag . (8))
                (n1 . 1)
                (n1 . 0)
                (n1 . 0)
                (n1 . 0))))
;; => [0 1 1 0 0 1 0 0 0 0 0 0]

[7 ?e ?x ?a ?m ?p ?l ?e 3 ?c ?o ?m 0]


(process-send-string
 (get-process "dns-lookup")
 (f-read-bytes "example-dns.bin"))
;; => nil

(with-current-buffer (get-buffer "*dns-lookup*")
  (set-buffer-multibyte nil)
  (setq buffer-file-coding-system 'binary)
  (f-write-bytes
   (buffer-substring-no-properties (point-min) (point-max))
   "out"))

(bindat-unpack
 '((ip ip))
 [#x5d #xb8 #xd8 #x22])
93.184.216.34
;; => ((ip . [93 184 216 34]))



(require 'bindat)
;; => bindat

(bindat-unpack '((x bits 2))
               [#x28 #x12]) 
;; => ((x 1 4 11 13))

(vconcat
 (bindat-pack '((x bits 2))
              '((x 1 4 11 13))))
;; => [40 18]

(setq x
      '((_ u8 1)
        (id vec (eval last))))
;; => ((_ u8 1) (id vec (eval last)))

(bindat-unpack
 '((k repeat
      (eval
       (let ((i bindat-idx)
             (count 0)
             step)
         (while (not (zerop (setq step (aref bindat-raw i))))
           (message "=> %S" step)
           (cl-incf count)
           (cl-incf i (1+ step)))
         count))
      (struct x)))
 [3 ?a ?b ?c 2 ?d ?f 0])
;; => ((k ((id . [97 98 99]) (_ . 3)) ((id . [100 102]) (_ . 2))))


?3
;; => 51



((k
  ((id . [97 98 99]) (_ . 3))
  ((id . [100 102]) (_ . 2))))

bindat-raw
bindat-idx

(let ((i bindat-idx)
      (count 0)
      step)
  (while (not (zerop (setq step (aref bindat-raw i))))
    (cl-incf count)
    (cl-incf i step))
  count)


;; => ((k (id . [97 98 99]) (_ . 3)))



;; => ((id . [97 98 99]) (_ . 3))

;; => ((id . [97]))

;; => ((id . [97 98 99]))

;; => ((id . [97 98 99]))


(bindat-unpack
 '((VER u8)
   (CMD u8)
   (DSTPORT u16)
   (DSTIP ip)
   (ID strz (eval (- (length bindat-raw) bindat-idx))))
 [#x04 #x01 #x00 #x50 #x5d #xb8 #xd8 #x22 #x46 #x72 #x65 #x64 #x00])
;; => ((ID . "Fred") (DSTIP . [93 184 216 34]) (DSTPORT . 80) (CMD . 1) (VER . 4))