Emacs speed up 1000%

I'm still NOT satisfied with my Emacs performance after applying below tricks:

  • autoload packages
  • idle-load packages
  • compiling *.el to *.elc

After some research, I found I could make my Emacs 1000% fast in 1 minute.

Please note I'm talking about the general performance not just startup time.

The solution is really simple.

Since I'm a Linux guy and my computer got enough (24G) memory. I can place my setup on memory only.

Step 1, insert below line into /etc/fstab and restart computer:

tmpfs       /tmp        tmpfs       nodev,nosuid,size=8G    0   0

Step 2, run the script "emacs2ram":

#!/bin/sh

if [ -z "$1" ];then
    echo "Usage:"
    echo "  emacs2ram start"
    echo "  emacs2ram restore"
    exit 1
fi

if [ "$1" == "start" ];then
    backup=emacs.d-backup
    link=.emacs.d
    volatile=/tmp/.emacs.d-$USER

    IFS=
    set -efu

    cd ~/

    if [ ! -r $volatile ]; then
        mkdir -m0700 $volatile
    fi

    # link -> volatie does not exist
    if [ "$(readlink $link)" != "$volatile" ]; then
        # backup project at first
        mv $link $backup
        # create the link
        ln -s $volatile $link
    fi

    if [ -e $link/.unpacked ]; then
        echo "Sync .emacs.d from memory to backup ..."
        rsync -avq --delete --exclude .unpacked ./$link/ ./$backup/
        echo "DONE!"
    else
        echo "Sync .emacs.d from disk to memory ..."
        rsync -avq ./$backup/ ./$link/
        touch $link/.unpacked
        echo "DONE!"
    fi
else
    echo "Moving .emacs.d back to disk ..."
    backup=$2-backup
    link=$2
    volatile=/tmp/$2-$USER
    cd ~/projs
    rm $link && mv $backup $link && rm -rf $volatile
    echo "DONE!"
fi

That's all! Please enjoy Emacs as usual.

The original script is from ArchLinux Wiki. I learned this technique eight years ago. I'm just wondering why I need eight years to apply it?

BTW, I've also moved all my projects into memory, using similar scripts.

UPDATE: I also publicize my project-managing script at gist. It's almost same as emacs2ram.

Use git-timemachine with Evil

git-timemachine is simple.

After "M-x git-timemachine", a new read-only buffer is created and I need only use six key bindings: "n", "q", "p", "w", "W", "g".

The problem is I'm using Evil. How to make git-timemachine's key bindings override Evil's?

I consulted the issue with Frank Fischer.

The solution is simple. Insert below code into ~/.emacs:

;; @see https://bitbucket.org/lyro/evil/issue/511/let-certain-minor-modes-key-bindings
(eval-after-load 'git-timemachine
  '(progn
     (evil-make-overriding-map git-timemachine-mode-map 'normal)
     ;; force update evil keymaps after git-timemachine-mode loaded
     (add-hook 'git-timemachine-mode-hook #'evil-normalize-keymaps)))

I also recommend reading his in-depth discussion on general handling of keybindings conflict between evil and other minor modes.

最佳作息时间表

7:00 起床时刻 需要一杯温开水
7:20-8:00 丰盛早餐 补充血糖要丰盛
8:30-9:00 避免运动 非运动最佳时间
9:00-10:30 困难工作 是工作最佳时间
10:30 眼睛休息 看窗外眼睛累了
11:00 吃点水果 血糖可能有下降
12:00-12:30 多吃豆类 豆类是很棒食物
13:00-14:00 小睡一会 会让你精力充沛
16:00 一杯酸奶 酸奶零负担零食
19:00 锻炼时间 快步走慢跑游泳
20:00 电视或书 工作太辛苦放松
22:00 洗热水澡 降温清洁利睡眠
22:30 上床睡觉 保证充足的睡眠

Code search in Emacs

After shutdown of Google Code Search, I turned to plain google search instead.

For example, if I want to search Emacs Lisp code. I google "keyword filetype:el".

"el" is the file extension of Emacs Lisp file.

Since I use Emacs for everything, it's natural to search code in Emacs.

So here is my solution.

Step 1, install w3m and its Emacs wrapper.

Step 2, insert below code into ~/.emacs:

(defun w3m-get-url-from-search-engine-alist (k l)
  (let (rlt)
    (if (listp l)
      (if (string= k (caar l))
          (setq rlt (nth 1 (car l)))
        (setq rlt (w3m-get-url-from-search-engine-alist k (cdr l)))))
    rlt))

(defun w3m-set-url-from-search-engine-alist (k l url)
    (if (listp l)
      (if (string= k (caar l))
          (setcdr (car l) (list url))
        (w3m-set-url-from-search-engine-alist k (cdr l) url))))

;; C-u S g RET <search term> RET in w3m
(setq w3m-search-engine-alist
      '(("g" "http://www.google.com.au/search?q=%s" utf-8)
        ;; stackoverflow search
        ("q" "http://www.google.com.au/search?q=%s+site:stackoverflow.com" utf-8)
        ;; elisp code search
        ("s" "http://www.google.com.au/search?q=%s+filetype:el"  utf-8)
        ;; wikipedia
        ("w" "http://en.wikipedia.org/wiki/Special:Search?search=%s" utf-8)
        ;; online dictionary
        ("d" "http://dictionary.reference.com/search?q=%s" utf-8)
        ;; javascript search on mozilla.org
        ("j" "http://www.google.com.au/search?q=%s+site:developer.mozilla.org" utf-8)))

(defun w3m-google-by-filetype ()
  (interactive)
  (unless (featurep 'w3m)
    (require 'w3m))
  (let ((thing (if (region-active-p)
                   (buffer-substring-no-properties (region-beginning) (region-end))
                 (thing-at-point 'symbol)))
        (old-url (w3m-get-url-from-search-engine-alist "s" w3m-search-engine-alist))
        new-url)
    (when buffer-file-name
      (setq new-url (replace-regexp-in-string
                     "filetype:.*"
                     (concat "filetype:" (file-name-extension buffer-file-name))
                     old-url))
      (w3m-set-url-from-search-engine-alist "s" w3m-search-engine-alist new-url))
    ;; change the url to search current file type
    (w3m-search "s" thing)
    ;; restore the default url
    (w3m-set-url-from-search-engine-alist "s" w3m-search-engine-alist old-url)))

Step 3, `M-x w3m-google-by-filetype`. Either the selected region or the symbol under cursor will be searched.

Use plain Vim to merge conflicts

I will explain how to resolve code merge conflicts with plain vim. The only third party plugin required is vim-unimpaired.

The reason to use vim is that I can't use vimdiff as a merge tool for certain VCS (CVS, Subversion, Perforce, …).

1 Problem

A typical file in conflict is like:

hello world
hello world
<<<<<<< .mine
This is fun stuff!
=======
This is a documentation file
>>>>>>> .r6
bye world
bye world

Let's call lines like ">>>>>>>", "<<<<<<<" and "=====" conflict markers.

Resolving conflicts is the operation of picking up either or both of the sections between markers.

2 Basic knowledge of Vim

  • "]n" move the focus to the next marker and "[n" to the previous marker.
  • "dd" delete current line
  • "d]n" delete from current line to the next marker (next marker exclusive)

3 Solution

Our workflow is actually simple:

  • Step 1, find the next marker by pressing "]n"
  • Step 2, "]nd]n[ndd[ndd" to pick up the section 1
  • Step 3, "d]ndd]ndd" to pick up the section 2, go to step 1

This workflow is compatible with any version control software.

Optionally you can set the shortcut for step 2 and step 3:

map <leader>dg1 ]nd]n[ndd[ndd
map <leader>dg2 d]ndd]ndd

Easy indentation setup in Emacs for web development

Because I've been asked on this issue so many times, I will anwser here once for all.

Insert below code into ~/.emacs:

(defun my-setup-indent (n)
  ;; web development
  (setq coffee-tab-width n) ; coffeescript
  (setq javascript-indent-level n) ; javascript-mode
  (setq js-indent-level n) ; js-mode
  (setq js2-basic-offset n) ; js2-mode
  (setq web-mode-markup-indent-offset n) ; web-mode, html tag in html file
  (setq web-mode-css-indent-offset n) ; web-mode, css in html file
  (setq web-mode-code-indent-offset n) ; web-mode, js code in html file
  (setq css-indent-offset n) ; css-mode
  )

(defun my-office-code-style ()
  (interactive)
  (message "Office code style!")
  (setq indent-tabs-mode t) ; use tab instead of space
  (my-setup-indent 4) ; indent 4 spaces width
  )

(defun my-personal-code-style ()
  (interactive)
  (message "My personal code style!")
  (setq indent-tabs-mode nil) ; use space instead of tab
  (my-setup-indent 2) ; indent 2 spaces width
  )

Combine above functions with `prog-mode-hook` and `buffer-file-name`, you can do the per project setup easily.

I do this thing when switching project. It takes me no more than one minute. Since my project last at least three months, one minute is not a big deal.

Effective code navigation for web development

I use AngularJS as an example. The technique applies to other web frameworks.

1 Problem

In Angular application, a function is defined in a javascript file and called in a html file.

javascript file:

<div ng-controller="MyController" ng-init="init()">
  <button ng-click="onClick()"></button>
</div>

html file:

angular.module['myapp'].controller('MyController', function ($scope, $http) {

  $scope.init = function () {
  };

  $scope.onClick= function() {
  };
});

As you can see, the keyword we are interested is just a string, like "MyController". The only way to extract it is regular expression.

2 Solution

CTags is good at parsing files with regular expression.

I use CTags instead of IDEs because:

  • New web frameworks keep popping up. I don't want to waste time&money on upgrading IDE frequently
  • As a freelancer, I have to deal with in-house frameworks which IDE developers NEVER heard of
  • Setup of ctags and text editors is easy. Once the first project is set up, others are just five minutes of copy&paste jobs.

2.1 CTags setup

Create a file named ".ctags":

--langdef=html
--langmap=html:.htm.html.erb
--regex-html=/[ \t]+id[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-controller[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-click[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-change[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-show[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-if[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-blur[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-focus[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-disabled[ \t]*=[ \t]*['"]([^'"]+)/\1/o,object/
--regex-html=/[ \t]+ng-repeat[ \t]*=[ \t]*['"][^'"]+ in \([^'"]+\)['"]/\1/o,object/

--langdef=js
--langmap=js:.js
--regex-js=/\$scope\.([A-Za-z0-9._\$]+)[ \t]*[:=]/\1/,variable/
--regex-js=/\.controller[ \t]*\([ \t]*['"]([A-Za-z0-9_$.]+)['"][ \t]*,/\1/,controller/
--regex-js=/\.filter[ \t]*\([ \t]*['"]([A-Za-z0-9_$.]+)['"][ \t]*,/\1/,filter/
--regex-js=/\.factory[ \t]*\([ \t]*['"]([A-Za-z0-9_$.]+)['"][ \t]*,/\1/,factory/
--regex-js=/\.service[ \t]*\([ \t]*['"]([A-Za-z0-9_$.]+)['"][ \t]*,/\1/,service/
--regex-js=/\.directive[ \t]*\([ \t]*['"]([A-Za-z0-9_$.]+)['"][ \t]*,/\1/,directive/
--regex-js=/.*[.][\s]*module\(['"]([a-zA-Z0-9_.]+)['"]/\1/m,module/
--regex-js=/[.](when\(['"][a-zA-Z0-9_\/]+['"])/\1/r,ngRoute/
--regex-js=/(,|(;|^)[ \t]*(var|let|([A-Za-z_$][A-Za-z0-9_$.]+\.)*))[ \t]*([A-Za-z0-9_$]+)[ \t]*=[ \t]*\{/\5/,object/
--regex-js=/(,|(;|^)[ \t]*(var|let|([A-Za-z_$][A-Za-z0-9_$.]+\.)*))[ \t]*([A-Za-z0-9_$]+)[ \t]*=[ \t]*function[ \t]*\(/\5/,function/
--regex-js=/(,|(;|^)[ \t]*(var|let|([A-Za-z_$][A-Za-z0-9_$.]+\.)*))[ \t]*([A-Za-z0-9_$]+)[ \t]*=[ \t]*\[/\5/,array/
--regex-js=/(,|(;|^)[ \t]*(var|let|([A-Za-z_$][A-Za-z0-9_$.]+\.)*))[ \t]*([A-Za-z0-9_$]+)[ \t]*=[ \t]*[^"]'[^']*/\5/,string/
--regex-js=/(,|(;|^)[ \t]*(var|let|([A-Za-z_$][A-Za-z0-9_$.]+\.)*))[ \t]*([A-Za-z0-9_$]+)[ \t]*=[ \t]*(true|false)/\5/,boolean/
--regex-js=/(,|(;|^)[ \t]*(var|let|([A-Za-z_$][A-Za-z0-9_$.]+\.)*))[ \t]*([A-Za-z0-9_$]+)[ \t]*=[ \t]*[0-9]+/\5/,number/
--regex-js=/(,|(;|^)[ \t]*(var|let|([A-Za-z_$][A-Za-z0-9_$.]+\.)*))[ \t]*([A-Za-z0-9_$]+)[ \t]*=[ \t]*.+([,;=]|$)/\5/,variable/
--regex-js=/(,|(;|^)[ \t]*(var|let|([A-Za-z_$][A-Za-z0-9_$.]+\.)*))[ \t]*([A-Za-z0-9_$]+)[ \t]*[ \t]*([,;]|$)/\5/,variable/
--regex-js=/function[ \t]+([A-Za-z0-9_$]+)[ \t]*\([^)]*\)/\1/,function/
--regex-js=/(,|^)[ \t]*([A-Za-z_$][A-Za-z0-9_$]+)[ \t]*:[ \t]*\{/\2/,object/
--regex-js=/(,|^)[ \t]*([A-Za-z_$][A-Za-z0-9_$]+)[ \t]*:[ \t]*function[ \t]*\(/\2/,function/
--regex-js=/(,|^)[ \t]*([A-Za-z_$][A-Za-z0-9_$]+)[ \t]*:[ \t]*\[/\2/,array/
--regex-js=/(,|^)[ \t]*([A-Za-z_$][A-Za-z0-9_$]+)[ \t]*:[ \t]*[^"]'[^']*/\2/,string/
--regex-js=/(,|^)[ \t]*([A-Za-z_$][A-Za-z0-9_$]+)[ \t]*:[ \t]*(true|false)/\2/,boolean/
--regex-js=/(,|^)[ \t]*([A-Za-z_$][A-Za-z0-9_$]+)[ \t]*:[ \t]*[0-9]+/\2/,number/
--regex-js=/(,|^)[ \t]*([A-Za-z_$][A-Za-z0-9_$]+)[ \t]*:[ \t]*[^=]+([,;]|$)/\2/,variable/

The key point of this setup is the regular expression for HTML should be as simple as possible!

On Mac, location of ".ctags":

/Users/yourname/.ctags

On Linux, location of ".ctags":

/home/yourname/.ctags

On Windows, place ".ctags" anywhere. Then create an environment variable named "HOME" whose value is the parent directory of ".ctags".

3 Sublime Text

Install CTags plugin and follow its instruction.

4 Emacs

Run CTags at least once:

ctags -e -R -f /app/path/TAGS

Then insert below code into your ~/.emacs:

(setq tags-table-list '("/app/path/TAGS"))

Open Emacs and `M-x find-tag` to start code navigation. That's all.

If you prefer Helm UI, install it and `M-x helm-etags-select` instead.

Please install the latest helm because I enhanced helm-etags-select for this problem.

Autocomplete with a dictionary and hippie-expand

I use company-mode most of the time. Sometimes I use Hippie Expand to autocomplete the English words from a dictionary.

I surely can do this in company-mode too. But I prefer hippie-expand because I choose to make company-mode focus on programming stuff and hippie-expand on writing.

1 Solution

This solution works in any environment.

  • Step1 (OPTIONAL), download english-words.txt and place it under "~/.emacs.d/misc/".
  • Step 2, Copy below setup into ~/.emacs and use key binding "M-/" to complete the word:
(global-set-key (kbd "M-/") 'hippie-expand)

;; The actual expansion function
(defun try-expand-by-dict (old)
  ;; old is true if we have already attempted an expansion
  (unless (bound-and-true-p ispell-minor-mode)
    (ispell-minor-mode 1))

  ;; english-words.txt is the fallback dicitonary
  (if (not ispell-alternate-dictionary)
      (setq ispell-alternate-dictionary (file-truename "~/.emacs.d/misc/english-words.txt")))
  (let ((lookup-func (if (fboundp 'ispell-lookup-words)
                       'ispell-lookup-words
                       'lookup-words)))
    (unless old
      (he-init-string (he-lisp-symbol-beg) (point))
      (if (not (he-string-member he-search-string he-tried-table))
        (setq he-tried-table (cons he-search-string he-tried-table)))
      (setq he-expand-list
            (and (not (equal he-search-string ""))
                 (funcall lookup-func (concat (buffer-substring-no-properties (he-lisp-symbol-beg) (point)) "*")))))
    (if (null he-expand-list)
      (if old (he-reset-string))
      (he-substitute-string (car he-expand-list))
      (setq he-expand-list (cdr he-expand-list))
      t)
    ))

(setq hippie-expand-try-functions-list
      '(;; try-expand-dabbrev
        ;; try-expand-dabbrev-all-buffers
        try-expand-by-dict))

2 Technical details

  • based on ac-ispell
  • lazy load of ispell-mode to speed Emacs startup
  • add a fallback dictionary "english-words.txt" so autocompletion never fails
  • ispell-lookup-words or lookup-words simply does grep thing, so english-words.txt is just a plain text file.

Why people say "Emacs is the best operating system"

It's becasue Emacs has great documentation and enough APIs.

1 Documentation

Its quality is the best.

It helps me even on non-Emacs stuff.

When configuring the font for Terminator (a terminal emulator) I found its manual is NOT clear. The manual didn't explain what is "Pango font name". After googling, I found the Emacs manual which explained it and also provided related toolchain.

For example, "fc-list" is mentioned to list the installed fonts. Since terminal use Mono font, I can type below command:

fc-list | grep Mono

The output is like:

/usr/share/fonts/urw-fonts/n022003l.pfb: Nimbus Mono L:style=Regular
/usr/share/fonts/urw-fonts/n022024l.pfb: Nimbus Mono L:style=Bold Oblique
/usr/share/fonts/liberation-fonts/LiberationMono-Regular.ttf: Liberation Mono:style=Regular
/usr/share/fonts/urw-fonts/n022004l.pfb: Nimbus Mono L:style=Bold
/usr/share/fonts/liberation-fonts/LiberationMono-BoldItalic.ttf: Liberation Mono:style=Bold Italic
/usr/share/fonts/liberation-fonts/LiberationMono-Bold.ttf: Liberation Mono:style=Bold
/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc: 文泉驿等宽正黑,文泉驛等寬正黑,WenQuanYi Zen Hei Mono:style=Medium,中等
/usr/share/fonts/liberation-fonts/LiberationMono-Italic.ttf: Liberation Mono:style=Italic
/usr/share/fonts/urw-fonts/n022023l.pfb: Nimbus Mono L:style=Regular Oblique

Since Emacs manual explains the meaning of output, I know the font name "WenQuanYi Zen Hei Mono" is the key component of "Pango font name"

Because the manual also gives the details of other components of "Pango font name", the final "~/.config/terminator/config" is like:

[profiles]
  [[default]]
  use_system_font = False
  # @see emacs manual for tools and explanation of font formats
  # https://www.gnu.org/software/emacs/manual/html_node/emacs/GTK-Resource-Basics.html
  # https://www.gnu.org/software/emacs/manual/html_node/emacs/Fonts.html#Fonts
  font =  WenQuanYi Zen Hei Mono 16

2 API

I create a new Emacs plugin which lists file names containing Chinese characters. At beginning I use Emacs API "find-name-dired". It uses the GNU find as the backend. But there is some integration issue on Linux. Chinese file names are displayed as garbled text in Emacs. I guess there is some decode/encode error between the interaction of Emacs process and GNU find process because Chinese files are fine on OS X.

Emacs has enough APIs. Later I found another pure Lisp API "find-lisp-find-dired". Problem solved.

Demo blog post created by nikola&org2nikola

python code:


import getopt, sys
if __name__ == '__main__':
    opts, args = getopt.getopt(sys.argv[1:], "hf:", ["help", "file="])
    for o, a in opts:
        if o in ("-h", "--help"):
            sys.exit()
        else:
            assert False, "unhandled option"
    print "hello world";

This is table:

Name Description Alternatives
Evil convert Emacs into vim none
org-mode Get Things Done (GTD) none
company-mode code completion auto-complete
expand-region selection region efficiently none

This is list:

  • list item 1
  • list item 2
  • list item 3
  • list item 4