麻婆豆腐

要点,

  • 嫩豆腐焯水去豆腥味, 加盐提味,一点酱油提色
  • 牛肉末炒酥取出
  • 加豆瓣酱,豆豉,辣椒面等.不能加姜,会压住其他味.炒到发香.加肉末,加高汤
  • 汤最多到豆腐的50%
  • 用炒勺的背面推豆腐保持豆腐完整
  • 三次勾芡(第一次让味进入豆腐,第二次起到拉力作用,第三次彻底粘合,不要吐水出来)
  • 最后用小火甚至微火,多烧一下才入味(要不断推豆腐,否则豆腐沾锅底)
  • 起锅前加入蒜苗, 少许花椒粒烤热后磨成粉撒入

mapotofu.jpg

视频, Youtube Dropbox

Set up vmtouch systemd service

"/etc/default/vmtouch" on Debian Testing,

# Change to yes to enable running vmtouch as a daemon
ENABLE_VMTOUCH=yes

# User and group to run as
VMTOUCH_USER_GROUP=cb:cb

# Whitespace separated list of files and directories for vmtouch to operate on
VMTOUCH_FILES="/home/cb/.emacs.d/lisp /home/cb/.emacs.d/elpa /home/cb/.emacs.d/site-lisp /home/cb/.mozilla/firefox/linux.default/*.sqlite /home/cb/.mozilla/firefox/linux.default/*.json"

# Options to pass to vmtouch itself. See vmtouch(8).
VMTOUCH_OPTIONS="-q -t"

Run sudo systemctl restart vmtouch to restart the service.

"home/cb.emacs.d" is not touched because the package elpy will create a sub-directory "elpy" there. This sub-directory is huge. It contains many python libraries.

Org link and pdf-tools

By default, Org pdf link uses doc-view.el to open pdf. So if you move focus over the link docview:papers/last.pdf::NNN in a org file and run M-x org-open-at-point, API doc-view-goto-page is called.

These days pdf-tools is very popular. If pdf-tools is installed and enabled, API call of doc-view-goto-page will fail.

Below code fixes this problem. It will automatically call correct API with or without pdf-tools.

(defun my-org-docview-open-hack (orig-func &rest args)
  (let* ((link (car args)) path page)
    (string-match "\\(.*?\\)\\(?:::\\([0-9]+\\)\\)?$" link)
    (setq path (match-string 1 link))
    (setq page (and (match-beginning 2)
                    (string-to-number (match-string 2 link))))
    (org-open-file path 1)
    (when page
      (cond
       ((eq major-mode 'pdf-view-mode)
        (pdf-view-goto-page page))
       (t
        (doc-view-goto-page page))))))
(advice-add 'org-docview-open :around #'my-org-docview-open-hack)

Use Magit to commit efficiently and correctly

I prefer using git cli because it's more light weight.

Here is my bash alias of git commit,

alias gc="git commit -m"

The problem of my "cli-only" workflow is it can't detect my mistakes automatically.

I often forget to add new code file into git. So my final commit might miss files.

Magit UI solution

One solution is to use Magit to commit inside Emacs. After commit, I could double check the files inside the hooks provided by Magit.

My set up in Emacs,

(defun my-lines-from-command-output (command)
  "Return lines of COMMAND output."
  (let* ((output (string-trim (shell-command-to-string command)))
         (cands (nonempty-lines output)))
    (delq nil (delete-dups cands))))

(defun my-hint-untracked-files ()
  "If untracked files and commited files share same extension, warn users."
  (let* ((exts (mapcar 'file-name-extension (my-lines-from-command-output "git diff-tree --no-commit-id --name-only -r HEAD")))
         (untracked-files (my-lines-from-command-output "git --no-pager ls-files --others --exclude-standard"))
         (lookup-ext (make-hash-table :test #'equal))
         rlt)
    ;; file extensions of files in HEAD commit
    (dolist (ext exts)
      (puthash ext t lookup-ext))
    ;; If untracked file has same file extension as committed files
    ;; maybe they should be staged too?
    (dolist (file untracked-files)
      (when (gethash (file-name-extension file) lookup-ext)
        (push (file-name-nondirectory file) rlt)))
    (when rlt
      (message "Stage files? %s" (mapconcat 'identity rlt " ")))))

(with-eval-after-load 'magit
  (defun my-git-check-status ()
    "Check git repo status."
    ;; use timer here to wait magit cool down
    (run-with-idle-timer 1 nil #'my-hint-untracked-files))
  (add-hook 'magit-post-commit-hook #'my-git-check-status)
  (add-hook 'git-commit-post-finish-hook #'my-git-check-status))

Screenshot of step 1 in Emacs, magit-commit-step1.png

Screenshot of step 2 (final step) in Emacs (I was reminded of untracked files "bye.js" and "tree.js" at the bottom of UI), magit-commit-step2.png

BTW, my actual code in my .emacs.d is a bit different.

CLI solution

Another solution is doing the git thing in shell plus Emacs "-batch" option.

Here is my bash setup,

function gc {
    # check my emacs.d exist
    if [ -f "$HOME/.emacs.d/README.org" ] && [ "$PWD" != "$HOME/.emacs.d" ]; then
        # magit hook does not work
        git commit -m "$@" && emacs -batch -Q -l "$HOME/.emacs.d/init.el" --eval "(my-hint-untracked-files)"
    else
        git commit -m "$@"
    fi
}

Please note running magit-commit-create in cli won't work. It's because magit-run-git-async in called and it might lock the git after the cli execution.

Screenshot in shell, magit-commit-in-shell.png

Linux audio input configuration

Run sudo alsamixer and press "F4" to choose audio input.

Make sure the right "Rear Mic" and "Capture" are enabled, linux-audio-input.png

Run alsamixer and check "pulseaudio" configuration in the same way.

Then test audio,

arecord --duration=5 --format=dat test-mic.wav && aplay test-mic.wav

Hardcore spell checking in Emacs

This article is not introduction of Emacs spell checking basics. It requires deep knowledge of Emacs Lisp and Fly Spell.

You could read my article What's the best spell check setup in emacs for basic knowledge.

This article introduces new techniques to make Fly Spell more powerful and faster.

The CLI programs aspell and hunspell can only parse plain text. They don't know any programming language syntax.

Fly Spell feeds the output of CLI program into its own Lisp predicate named flyspell-generic-check-word-predicate whose default value is nil.

When executing (flyspell-mode 1), the per mode predicate is assigned to flyspell-generic-check-word-predicate.

For example, you can run (get major-mode 'flyspell-mode-predicate) to get predicate of current major mode, (get 'web-mode 'flyspell-mode-predicate) to get predicate of web-mode.

The predicate is a simple function without parameter. Here is my predicate for web-mode,

(defun my-web-mode-flyspell-verify ()
  "Fly Spell predicate of `web-mode`."
  (let* ((font-face-at-point (get-text-property (- (point) 1) 'face))
         rlt)
    ;; If rlt is t, the word at point is POSSIBLY a typo, continue checking.
    (setq rlt t)
    ;; if rlt is nil, the word at point is definitely NOT a typo.
    ;; (setq rlt nil)
    rlt))
;; Attach my predicate to `web-mode`
(put 'web-mode 'flyspell-mode-predicate 'my-web-mode-flyspell-verify)

If you read code of flyspell-prog-mode, you will find it set flyspell-generic-check-word-predicate to its own predicate flyspell-generic-progmode-verify,

(defvar flyspell-prog-text-faces
  '(font-lock-string-face font-lock-comment-face font-lock-doc-face)
  "Faces corresponding to text in programming-mode buffers.")

(defun flyspell-generic-progmode-verify ()
  "Used for `flyspell-generic-check-word-predicate' in programming modes."
  (unless (eql (point) (point-min))
    ;; (point) is next char after the word. Must check one char before.
    (let ((f (get-text-property (1- (point)) 'face)))
      (memq f flyspell-prog-text-faces))))

As you can see, flyspell-generic-progmode-verify is very simple. If the word at point is not inside comment or string, the predicate returns nil which means the word is not a typo.

So in theory I can write my own predicate by following flyspell-generic-progmode-verify.

But in reality it's not as simple as it seems. The predicate is written in Lisp so it's slow. If it contains too much code, Fly Spell process might block other actions in Emacs. Emacs could be un-responsive when editing text.

The solution is not to start Fly Spell process too frequently.

The flyspell-mode starts checking when text in current buffer is modified.

My solution is not to turn on flyspell-mode. Instead, I manage the spell checking by myself using APIs from flyspell.

I only spell check when user saving current buffer. The interval between spell check should not be less than 5 minutes. Spell check is done by calling API flyspell-buffer

Checking the whole buffer is still slow. Instead, we can check the text region in current window by calling flyspell-region instead. The api window-total-height returns the height of current Windows. So I can use below code to get the region to check,

(let* (beg end (orig-pos (point)))
  (save-excursion
    (forward-line (- (window-total-height)))
    (setq beg (line-beginning-position))
    (goto-char orig-pos)
    (forward-line (window-total-height))
    (setq end (line-end-position)))
  (flyspell-region beg end))

I also need respect the predicate embedded in the major mode in my own generic predicate. Since per mode predicate has already checked the font face, I should skip the font face check in generic predicate if per mode predicate exists.

Above algorithms are implemented in wucuo.

Usage,

(add-hook 'prog-mode-hook 'wucuo-start)
(add-hook 'text-mode-hook 'wucuo-start)

If wucuo-flyspell-start-mode is "fast" (default value), flyspell-region is used, visible region is checked when user saves current file.

If wucuo-flyspell-start-mode is "normal", flyspell-buffer is used, current buffer is checked when user saves current file.

Audio recording on Linux

  • Run sudo alsamixer and turn off mic to reduce the noise
  • Run alsamixer to double check pulse setup
  • Make sure correct device is selected in audacity
  • Restart audacity and test

My alsamixer setup, alsamixer-nq8.png

Use Magit API to rebase to closest branch

My workflow in Git is,

  • Create a new feature branch based on main branch
  • Add some small commits into feature branch
  • Rebase feature branch interactively

The final rebase step happens a lot.

So I could use Magit api magit-rebase-interactive to speed up it.

The key is to analyze output of git log --decorate --oneline to find the main branch commit.

Code,

(defun my-git-extract-based (target)
  "Extract based version from TARGET."
  (replace-regexp-in-string "^tag: +"
                            ""
                            (car (nreverse (split-string target ", +")))))

(defun my-git-rebase-interactive (&optional user-select-branch)
  "Rebase interactively on the closest branch or tag in git log output.
If USER-SELECT-BRANCH is not nil, rebase on the tag or branch selected by user."
  (interactive "P")
  (let* ((log-output (shell-command-to-string "git --no-pager log --decorate --oneline -n 1024"))
         (lines (split-string log-output "\n"))
         (targets (delq nil
                        (mapcar (lambda (e)
                                  (when (and (string-match "^[a-z0-9]+ (\\([^()]+\\)) " e)
                                             (not (string-match "^[a-z0-9]+ (HEAD " e)))
                                    (match-string 1 e))) lines)))
         based)
    (cond
     ((or (not targets) (eq (length targets) 0))
      (message "No tag or branch is found to base on."))
     ((or (not user-select-branch)) (eq (length targets) 1)
      ;; select the closest/only tag or branch
      (setq based (my-git-extract-based (nth 0 targets))))
     (t
      ;; select the one tag or branch
      (setq based (my-git-extract-based (completing-read "Select based: " targets)))))

    ;; start git rebase
    (when based
      (magit-rebase-interactive based nil))))

Screencast:

magit-rebase-api.gif

Make Emacs faster than Vim in "git mergetool"

My article Emacs is the best merge tool for Git explains how to combine git mergetool with ediff-mode in Emacs.

Harrison McCullough suggested the work flow can be faster if emacs is replaced with emacsclient.

I did some research and found a perfect solution. It's even faster than Vim.

Initial solution

Please note emacsclient is only use for resolving conflicts.

Step 1, start emacs server by running emacs -Q --daemon --eval "(setq startup-now t)" -l "/home/my-username/.emacs.d/init.el" --eval "(progn (require 'server) (server-start))" in shell.

Step 2, insert below code into ~/.emacs.d/init.el (see the comment why this advice is required):

(defadvice server-save-buffers-kill-terminal (after server-save-buffers-kill-terminal-after-hack activate)
  ;; kill all buffers, so new ediff panel is re-created and `ediff-startup-hook-setup' is called again
  ;; besides, remove the buffers whose binding files are alredy merged in `buffer-list'
  (mapc 'kill-buffer (buffer-list)))

Step 3, insert below code into ~/.gitconfig:

[mergetool.ediff]
cmd = emacsclient -nw --eval \"(progn (setq ediff-quit-hook 'kill-emacs) (if (file-readable-p \\\"$BASE\\\") (ediff-merge-files-with-ancestor \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$BASE\\\" nil \\\"$MERGED\\\") (ediff-merge-files \\\"$LOCAL\\\" \\\"$REMOTE\\\" nil \\\"$MERGED\\\")))\"

My real world solution

It's similar to initial solution. But some scripts are created for automation.

Step 1, read Using Emacs as a Server in the manual and create ~/.config/systemd/user/emacs.service for Systemd:

[Unit]
Description=Emacs text editor
Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/

[Service]
Type=forking
ExecStart=emacs -Q --daemon --eval "(setq startup-now t)" -l "/home/my-username/.emacs.d/init.el" --eval "(progn (require 'server) (server-start))" 
ExecStop=emacsclient --eval "(kill-emacs)"
Environment=SSH_AUTH_SOCK=%t/keyring/ssh
Restart=on-failure

[Install]
WantedBy=default.target

Step 2, set up in ~/.gitconfig:

[mergetool.emacs]
    cmd = ediff.sh "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
[mergetool.emacsclient]
    cmd = MYEMACSCLIENT=emacsclient ediff.sh "$LOCAL" "$REMOTE" "$BASE" "$MERGED"

Step 3, create ediff.sh:

#!/bin/sh
[ -z "$MYEMACSCLIENT" ] && MYEMACSCLIENT="emacs"
# emacsclient won't work in git mergetool
# $1=$LOCAL $2=$REMOTE $3=$BASE $4=$MERGED
if [ "$MYEMACSCLIENT" = "emacs" ]; then
    $MYEMACSCLIENT -nw -Q --eval "(setq startup-now t)" -l "$HOME/.emacs.d/init.el" --eval "(progn (setq ediff-quit-hook 'kill-emacs) (if (file-readable-p \"$3\") (ediff-merge-files-with-ancestor \"$1\" \"$2\" \"$3\" nil \"$4\") (ediff-merge-files \"$1\" \"$2\" nil \"$4\")))"
else
    $MYEMACSCLIENT -nw --eval "(progn (setq ediff-quit-hook 'kill-emacs) (if (file-readable-p \"$3\") (ediff-merge-files-with-ancestor \"$1\" \"$2\" \"$3\" nil \"$4\") (ediff-merge-files \"$1\" \"$2\" nil \"$4\")))"
fi

Step 4, run git mergetool -t emacsclient to resolve conflicts.

My init-ediff.el in emacs.d.

Thoughts on "Native shell completion in Emacs"

Native shell completion in Emacs by Troy Hinckley is must read for completion in shell-mode.

One problem is my ~/.bashrc executes /etc/bash_completion,

if [ -f /etc/bash_completion ]; then
    . /etc/bash_completion
fi

Unfortunately /etc/bash_completion makes complete -p output some lines the Emacs function bash-completion-tokenize can't analyze.

Here is output of complete -p at my PC,

...
complete -F _known_hosts mtr
complete -o default -o nospace -W 'homepc
192.168.1.104
github.com
gitlab.com' scp
complete -o default -f -X '!*.dvi' dvipdf
...

The line gitlab.com' scp will crash bash-completion-tokenize. Obviously, one line complete -o default -o nospace -W 'homepc 192.168.1.104 github.com gitlab.com' scp is wrongly split into multiple lines by complete -p.

In shell-mode, completion functions might call bash-completion-tokenize. If bash-completion-tokenize crashes, the completion in shell-mode won't work.

Besides, if company-mode provides auto-completion UI, it's better to place the backend company-files before company-native-complete. It's because the backend company-files displays the full file path in candidates. So users can complete the whole path in one shot.

My setup code for the packages Troy Hinckley suggested,

;; Enable auto-completion in `shell'.
(with-eval-after-load 'shell
  (native-complete-setup-bash))

;; `bash-completion-tokenize' can handle garbage output of "complete -p"
(defadvice bash-completion-tokenize (around bash-completion-tokenize-hack activate)
  (let* ((args (ad-get-args 0))
         (beg (nth 0 args))
         (end (nth 1 args)))
    ;; original code extracts tokens from output of "complete -p" line by line
    (cond
     ((not (string-match-p "^complete " (buffer-substring beg end)))
      ;; filter out some wierd lines
      (setq ad-return-value nil))
     (t
      ad-do-it))))

(defun shell-mode-hook-setup ()
  "Set up `shell-mode'."
  ;; hook `completion-at-point', optional
  (add-hook 'completion-at-point-functions #'native-complete-at-point nil t)
  (setq-local company-backends '((company-files company-native-complete)))
  ;; `company-native-complete' is better than `completion-at-point'
  (local-set-key (kbd "TAB") 'company-complete))
(add-hook 'shell-mode-hook 'shell-mode-hook-setup)

Screenshot,

shell-complete-path-nq8.png

shell-complete-param-nq8.png