- =defsubst=
- 1 + 2 + 3 + … + 100
- =’(1 2 3)= ≠ =(list 1 2 3)=
- =cl-loop=
- parsec
- How does Emacs Lisp Completion work?
- 2^0 + 2^1 + 2^2 + … + 2^n = 2^(n+1) - 1
- 10^n % 7
- 7^n 的个位数
- How to extend pcase?
- S(n) = a_1 + a_2 + … + a_n
- variable vs function | Symbol
- Delete first element of list
- Transient
- Deal with Interactive from in Advice
- (info “(elisp) Programmed Completion”)
- 测试 Face
- pip 进度条
- Emacs Server
- 「实时」JQ 输出
- HTTP Range | 断点续传
- HTTP proxy via HTTP CONNECT
- dns lookup
使用 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)
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)
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 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))
(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)
(cl-loop for i from 1 to 100
sum i into result
finally return (format "1 + 2 + 3 ... + 100 = %s" result))
(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)
(cl-loop for i from 1
when (> i 100)
return "look ma, not catch/throw")
(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))
(cl-loop for (a b) on '(1 2 3 4 5 6) by #'cddr
collect (cons a b))
;; 2 维数组
(cl-loop with vv = (make-vector 4 nil)
for v across-ref vv
do (setq v (make-vector 3 0))
finally return vv)
(cl-loop for i from 1 to 3
vconcat (list i))
;; 2 维数组
(cl-loop repeat 4
vconcat (vector (make-vector 3 0)))
(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)))
(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-with-input "/* hello */"
(parsec-string "/*")
(parsec-many-till-as-string (parsec-any-ch)
(parsec-try
(parsec-string "*/"))))
(with-temp-buffer
(emacs-lisp-mode)
completion-at-point-functions)
(all-completions "emacs-lisp" obarray #'functionp)
(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)))
等比数列求和
(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)
(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)
(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)
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)))
(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))
(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)))))
(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)
(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)))
(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))
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))))))
(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))))
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)))
启动方法 | 形式 |
---|---|
M-x server-mode | 前台 |
emacs –daemon | 后台 |
Emacs daemon 就是在后台运行的程序,和 Emacs server 是两个不相干的概念。=emacs –daemon= 会自动开启 Emacs server(不然这个 daemon 不就用不来吗?)
为了演示 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 的官方客户端是 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 PID | Emacs process |
-window-system-unsupported | 不支持图形界面 |
-print STRING | 打印 -eval 结果 |
-print-nonl STRING | 继续 -print |
… | … |
~ $ 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)")
使用现成的 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
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 Query 同时显示输出
思路:post-command-hook + asynchronous process
以 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 Header。有些服务器支持,比如
curl -I example.com | tr -d "\r"
返回的 Accept-Ranges: bytes 就表示这个服务器支持 Range。
下面的 curl 命令会发送 “Range: bytes=-100”
curl -r -100 example.com
(let ((url-request-extra-headers '(("Range" . "bytes=-100"))))
(display-buffer
(url-retrieve-synchronously "http://example.com")))
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
(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))