Use Emacs to read English classic books efficiently

Classic books is difficult to read because they have many new words.

Find the word's dictionary definition is tedios and breaks the flow.

I tried reading those books in iPad and Kindle. The experience is not very smooth because,

  • It takes too much effort to select the word and send it to the dictionary. Particularly difficulty for me because I've got big fingers
  • The dictionary popup overlaps with the book's content. Feels not comfortable

In Emacs,

  • new words can be automatically highlighted
  • The book's content and dictionary are displayed side by side

The Emacs plugin mybigword 0.2.4 provides a new command mybigword-big-words-in-current-windows which automatically highlight the new words in current window with hint. Type the hint, the word's definition is displayed in another window.

Screenshot before running M-x mybigword-big-words-in-current-windows,

mybigword-before.png

After,

mybigword-after.png

The hint UI is actually based on avy.

Every English word can be measure by word frequency named Zipf Scale (see http://crr.ugent.be/papers/van_Heuven_et_al_SUBTLEX-UK.pdf for more details).

Any word with ZipF less than the value of mybigword-upper-limit is regarded as new word.

In my setup, mybigword-upper-limit is 3.6.

Code faster by extending Emacs EVIL text object

I use EVIL text object a lot. For example, press vi( to select the code snippet inside parenthesis.

I could also press vib to do exactly same thing as vi( because below code in EVIL's evil-maps.el,

(define-key evil-outer-text-objects-map "b" 'evil-a-paren)
(define-key evil-inner-text-objects-map "b" 'evil-inner-paren)

As a full stack web developer, I often need select the snippet inside "[]", "{}", "()", "<>". So I prefer using vig to replace vi[, vi{, vi(, and vi<.

Here is my new text object,

(defun my-evil-paren-range (count beg end type inclusive)
  "Get minimum range of paren text object.
COUNT, BEG, END, TYPE is used.  If INCLUSIVE is t, the text object is inclusive."
  (let* ((parens '("()" "[]" "{}" "<>"))
         range
         found-range)
    (dolist (p parens)
      (condition-case nil
          (setq range (evil-select-paren (aref p 0) (aref p 1) beg end type count inclusive))
        (error nil))
      (when range
        (cond
         (found-range
          (when (< (- (nth 1 range) (nth 0 range))
                   (- (nth 1 found-range) (nth 0 found-range)))
            (setf (nth 0 found-range) (nth 0 range))
            (setf (nth 1 found-range) (nth 1 range))))
         (t
          (setq found-range range)))))
    found-range))

(evil-define-text-object my-evil-a-paren (count &optional beg end type)
  "Select a paren."
  :extend-selection t
  (my-evil-paren-range count beg end type t))

(evil-define-text-object my-evil-inner-paren (count &optional beg end type)
  "Select 'inner' paren."
  :extend-selection nil
  (my-evil-paren-range count beg end type nil))

(define-key evil-inner-text-objects-map "g" #'my-evil-inner-paren)
(define-key evil-outer-text-objects-map "g" #'my-evil-a-paren)

In above code,

  • my-evil-paren-range returns the minimum range of text objects "[", "{", "(", and "<".
  • EVIL api evil-define-text-object is used to define a text object whose range is returned by my-evil-paren-range
  • In evil-outer-text-objects-map the text object shortcut "g" is defined

As you can see, understanding my personal workflow and knowing a bit Lisp does make me code faster.

豉汁蒸排骨

  • 一把豆豉洗净
  • 葱段
  • 一点点盐
  • 一勺淀粉
  • 几滴鱼露
  • 少许料酒
  • 少许蚝油
  • 少许白糖
  • 用手搅拌腌制半小时
  • 挑出排骨,不要豆豉
  • 第二次加淀粉搅拌,表面浇上花生油
  • 撒红辣椒点缀
  • 蒸15分钟, 上盘撒葱花

Youtube视频

视频备份

小结:

  • 如用大排做原料,需要用榔头敲松再切小块

萝卜丝饼

  • 半个白萝卜刮丝, 15秒沸水烫一下过冷水去萝卜生味.略挤干萝卜丝水分
  • 一小把粉丝焯水1分钟切碎
  • 一红辣椒或1/4红色大辣椒(主要是增色,辣味可选)切碎
  • 泡好的干虾仁切小颗粒
  • 萝卜丝打散,放入粉丝红辣椒虾仁,1小勺生姜泥,1/2小勺五香粉,1/2小勺白胡椒粉,最后葱花放在上面
  • 油2大勺烧至冒烟泼在葱花上(激发其香味)
  • 蚝油1.5小勺,香油1/2小勺,1/4小勺盐搅拌

发面煎饼略.

Youtube视频

小结,

  • 我以中东面饼代替中国煎饼

蓝莓山药

  • 山药洗净蒸15分钟, 去皮切片(或条块)
  • 蓝莓酱加少量炼乳(增加奶香味,可选)蜂蜜搅拌,浇于山药上

youtube视频 视频备份

小鸡炖蘑菇

  • 把榛蘑(honey mushroom)根部和脏的部分去掉,洗净,温水泡30分钟,沥干待用;粉条用清水泡软备用
  • 鸡清洗干净,切核桃块,易入味
  • 油热后放葱(半两)姜(半两)花椒(25粒),大料(两朵) 炒香,放鸡肉小火炒4或5分钟.
  • 放入少许酱油(不要盖住蘑菇味), 一只鸡一两酱油.
  • 蘑菇水小火40分钟
  • 下蘑菇小火20分钟
  • 加盐调口
  • 下粉条, 少许鸡精, 5分钟
  • 大火略收干水分

要点:

  • 油多一点热一点,把鸡肉煎表面金黄
  • 鸡肉块不要太小,否则煮久了肉都散掉了
    • 不要加料酒,会掩盖鸡肉和蘑菇的香味
  • 粉条口感很好.商业版不放粉条因为粉条15分钟后会变形,很难看
  • 我又加了青豆,10分钟就熟了,煮太久会变黄
  • 粉条要看种类,火锅用粉条特别耐煮,小火要15分钟才能熟,可以早放,普通粉条早放会融化沾锅底
  • 粉条也可以换成腐竹,豆腐泡
  • 蘑菇水至少一大碗(1升),这样一小时后正好
  • 还加了少许胡萝卜片,少许生菜,生菜最后5分钟加

B站视频 视频备份

Show files by date in SOME Emacs dired buffer

It's as simple as modify dired-actual-switches in dired-mode-hook.

Minimum setup,

(defvar my-dired-new-file-first-dirs
  '("bt/finished/$"
    "bt/torrents?/$"
    "documents?/$"
    "music/$"
    "downloads?/$")
  "Dired directory patterns where newest files are on the top.")

(defun my-dired-mode-hook-setup ()
  "Set up Dired."
  (when (cl-find-if (lambda (regexp)
                      (let ((case-fold-search t))
                        (string-match regexp default-directory)))
                my-dired-new-file-first-dirs)
    (setq dired-actual-switches "-lat")))
(add-hook 'dired-mode-hook 'my-dired-mode-hook-setup)

Here is my real world setup.

How to use EMMS effectively

First thing is to set up emms.

I could simply enable all the emms features in one line,

(with-eval-after-load 'emms (emms-all))

But above setup makes filtering tracks very slow because it's too heavy weight.

So I use below setup,

(with-eval-after-load 'emms
  ;; minimum setup is more robust
  (emms-minimalistic)

  ;; `emms-info-native' supports mp3,flac and requires NO cli tools
  (unless (memq 'emms-info-native emms-info-functions)
    (require 'emms-info-native)
    (push 'emms-info-native emms-info-functions))

  ;; extract track info when loading the playlist
  (push 'emms-info-initialize-track emms-track-initialize-functions)

  ;; I also use emms to manage tv shows, so I use mplayer only
  (setq emms-player-list '(emms-player-mplayer)))

Play mp3&flac in "~/Dropbox/music",

(defun my-music ()
  "My music."
  (interactive)
  (emms-stop)
  (when (bufferp emms-playlist-buffer-name)
    (kill-buffer emms-playlist-buffer-name))
  (emms-play-directory-tree "~/Dropbox/music")
  (emms-shuffle)
  (emms-next))

Sometimes I need focus on challenge programming tasks and emms should play only Mozart&Bach.

(defvar my-emms-playlist-filter-keyword "mozart|bach"
  "Keyword to filter tracks in emms playlist.
Space in the keyword matches any characters.
 \"|\" means OR operator in regexp.")

(defun my-strip-path (path strip-count)
  "Strip PATH with STRIP-COUNT."
  (let* ((i (1- (length path)))
         str)
    (while (and (> strip-count 0)
                (> i 0))
      (when (= (aref path i) ?/)
        (setq strip-count (1- strip-count)))
      (setq i (1- i)))
    (setq str (if (= 0 strip-count) (substring path (1+ i)) path))
    (replace-regexp-in-string "^/" "" str)))

(defun my-emms-track-description (track)
  "Description of TRACK."
  (let ((desc (emms-track-simple-description track))
        (type (emms-track-type track)))
    (when (eq 'file type)
      (setq desc (my-strip-path desc 2)))
    desc))

(defvar my-emms-track-regexp-function #'my-emms-track-regexp-internal
  "Get regexp to search track.")

(defun my-emms-track-regexp-internal (keyword)
  "Convert KEYWORD into regexp for matching tracks."
  (let* ((re (replace-regexp-in-string "|" "\\\\|" keyword)))
    (setq re (replace-regexp-in-string " +" ".*" re))))

(defun my-emms-track-match-p (track keyword)
  "Test if TRACK's information match KEYWORD."
  (let* ((case-fold-search t)
         (regexp (funcall my-emms-track-regexp-function keyword))
         s)
    (or (string-match regexp (emms-track-force-description track))
        (and (setq s (emms-track-get track 'info-genre)) (string-match regexp s))
        (and (setq s (emms-track-get track 'info-title)) (string-match regexp s))
        (and (setq s (emms-track-get track 'info-album)) (string-match regexp s))
        (and (setq s (emms-track-get track 'info-composer)) (string-match regexp s))
        (and (setq s (emms-track-get track 'info-artist)) (string-match regexp s)))))

(defun my-emms-show ()
  "Show information of current track."
  (interactive)
  (let* ((emms-track-description-function (lambda (track)
                                            (let ((composer (emms-track-get track 'info-composer))
                                                  (artist (emms-track-get track 'info-artist)))
                                              (concat (if composer (format "%s(C) => " composer))
                                                      (if artist (format "%s(A) => " artist))
                                                      (my-emms-track-description track))))))
    (emms-show)))

(defun my-emms-playlist-filter (&optional input-p)
  "Filter tracks in emms playlist.
If INPUT-P is t, `my-emms-playlist-random-track-keyword' is input by user."
  (interactive "P")
  ;; shuffle the playlist
  (when input-p
    (setq my-emms-playlist-filter-keyword
          (read-string "Keyword to filter tracks in playlist: ")))
  (with-current-buffer emms-playlist-buffer-name
    (goto-char (point-min))
    (let* ((case-fold-search t)
           track)
      (while (setq track (emms-playlist-track-at))
        (cond
         ((my-emms-track-match-p track my-emms-playlist-filter-keyword)
          (forward-line 1))
         (t
          (emms-playlist-mode-kill-track))))))

  (emms-random)
  ;; show current track info
  (my-emms-show))

As you can see, a little EMMS api knowledge could go a long way.

If you want to study EMMS API by practice, run M-x emms-playlist-mode-go, then M-x eval-expression RETURN (emms-playlist-track-at) to get the information of the track at point.

Here is my real world emms setup where you can see below code,

(defvar my-emms-track-regexp-function
  (lambda (str)
    ;; can search track with Chinese information
    (my-emms-track-regexp-internal (my-extended-regexp str)))
  "Get regexp to search track.")

So I can use Pinyin to search track's Chinese information. I don't know any other multimedia manager can do the same thing.

Add live demo to emacs package

Emacs package developers sometimes need add live demo to her/his project.

The requirement came from my discussion with pyim's developer.

With all the linting and unit tests running for ages, he still need a quick way to test if the package actually works on Emacs 25 (and other Emacs versions). I totally agree with him because my own projects have similar problems.

A live demo built into the project is very useful for developers and testers.

Besides, a live demo could help users. They try the new package with no hassle. They don't modify their own emacs configuration to try the new package.

So I figured out a simple solution. The best part is that any packages could use this solution with minimum change if their CI script is already set up.

In a package's CI script, Emacs is running in batch mode (with "–batch" option). What I suggest is add another Makefile task runemacs which is very similar to the original CI task. But in this task, the "–batch" options is removed.

See the solution I added for find-file-in-project,

diff --git a/Makefile b/Makefile
index 9005ca4..8f7a8ae 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,8 @@
 SHELL = /bin/sh
 EMACS ?= emacs
 PROFILER =
-EMACS_BATCH_OPTS=--batch -Q -l find-file-in-project.el
+EMACS_GENERIC_OPTS=-Q -L . -L deps/ivy-0.13.4
+EMACS_BATCH_OPTS:=--batch $(EMACS_GENERIC_OPTS)
 RM = @rm -rf

 .PHONY: test clean test compile
@@ -18,3 +19,8 @@ compile: clean
 # Run tests.
 test: compile
    @$(EMACS) $(EMACS_BATCH_OPTS) -l tests/ffip-tests.el
+
+runemacs:
+   @mkdir -p deps;
+   @if [ ! -f deps/ivy-0.13.4/ivy.el ]; then curl -L https://stable.melpa.org/packages/ivy-0.13.4.tar | tar x -C deps/; fi;
+   @$(EMACS) $(EMACS_GENERIC_OPTS) --load ./tests/emacs-init.el
diff --git a/tests/emacs-init.el b/tests/emacs-init.el
new file mode 100644
index 0000000..a4df068
--- /dev/null
+++ b/tests/emacs-init.el
@@ -0,0 +1,17 @@
+(require 'find-file-in-project)
+(require 'ivy)
+(ivy-mode 1)
+(setq ffip-match-path-instead-of-filename t)
+(run-with-idle-timer
+ 1
+ nil
+ (lambda ()
+   (erase-buffer)
+   (goto-char (point-min))
+   (insert
+    ";; Setup of this demo,\n"
+    "(setq ffip-match-path-instead-of-filename t)\n\n\n"
+    ";; Run \"M-x find-file-in-project-by-selected\" and input search keyword \"el\" or \"tests\".\n\n\n"
+    ";; Move cursor above below paths and run \"M-x find-file-in-project-at-point\",\n\n"
+    ";;   tests/ffip-tests.el ; open file directly \n"
+    ";;   find-file-in-project.el:50 ; open file and jump to line 50\n")))

Similar solution is also used in pyim, it's one liner in shell to test it in Emacs 25,

EMACS=/home/cb/what-ever-path/25.1/bin/emacs make runemacs

蒸茄盒

  • 两个茄子选软且颜色深,去皮.抹盐再冲掉(为了避免表面变黑),切茄盒
  • 葱姜末,料酒,肉米,全蛋,少许盐鸡精,花椒水三勺,二合一酱油20克,加淀粉适量(不用太多,2~3勺)锁住水分.搅拌制作成馅(口感类似午餐肉)
  • 茄盒加入馅,摆盘后洒三分口盐(盐引出茄子的汁),大火蒸15分钟,洒红绿尖椒末,蒸一分钟
  • 浇上酱油,加葱丝香菜,呲热油(速度要快)

视频: 在家炸茄盒太麻烦?试试茄盒这么做,从里到外软嫩咸香,鲜味十足 视频备份