Evil text object to select nearby file path

CREATED: <2015-08-08 Sat>

UPDATED: <2015-08-24 Mon>

Insert below code into ~/.emacs:

;; {{ nearby file path as text object,
;;      - "vif" to select only basename
;;      - "vaf" to select the full path
;;
;;  example: "/hello/world" "/test/back.exe"
;;               "C:hello\\hello\\world\\test.exe" "D:blah\\hello\\world\\base.exe"
;;
;; tweak evil-filepath-is-nonname to re-define a path
(defun evil-filepath-is-separator-char (ch)
  "Check ascii table"
  (let (rlt)
    (if (or (= ch 47)
            (= ch 92))
        (setq rlt t))
    rlt))

(defun evil-filepath-not-path-char (ch)
  "Check ascii table for charctater "
  (let (rlt)
    (if (or (and (<= 0 ch) (<= ch 32))
            (= ch 34) ; double quotes
            (= ch 39) ; single quote
            (= ch 40) ; (
            (= ch 41) ; )
            (= ch 60) ; <
            (= ch 62) ; >
            (= ch 91) ; [
            (= ch 93) ; ]
            (= ch 96) ; `
            (= ch 123) ; {
            (= ch 125) ; }
            (= 127 ch))
        (setq rlt t))
    rlt))

(defun evil-filepath-char-not-placed-at-end-of-path (ch)
  (or (= 44 ch) ; ,
      (= 46 ch) ; .
      ))

(defun evil-filepath-calculate-path (b e)
  (let (rlt f)
    (when (and b e)
      (setq b (+ 1 b))
      (when (save-excursion
                (goto-char e)
                (setq f (evil-filepath-search-forward-char 'evil-filepath-is-separator-char t))
                (and f (>= f b)))
        (setq rlt (list b (+ 1 f) (- e 1)))))
    rlt))

(defun evil-filepath-get-path-already-inside ()
  (let (b e)
    (save-excursion
      (setq b (evil-filepath-search-forward-char 'evil-filepath-not-path-char t)))
    (save-excursion
      (setq e (evil-filepath-search-forward-char 'evil-filepath-not-path-char))
      (when e
        (goto-char (- e 1))
        ;; example: hello/world,
        (if (evil-filepath-char-not-placed-at-end-of-path (following-char))
            (setq e (- e 1)))
        ))
    (evil-filepath-calculate-path b e)))

(defun evil-filepath-search-forward-char (fn &optional backward)
  (let (found rlt (limit (if backward (point-min) (point-max))) out)
    (save-excursion
      (while (not out)
        ;; for the char, exit
        (if (setq found (apply fn (list (following-char))))
            (setq out t)
          ;; reach the limit, exit
          (if (= (point) limit)
              (setq out t)
            ;; keep moving
            (if backward (backward-char) (forward-char)))))
      (if found (setq rlt (point))))
    rlt))

(defun evil-filepath-extract-region ()
  "Find the closest file path"
  (let (rlt
        b
        f1
        f2)

    (if (and (not (evil-filepath-not-path-char (following-char)))
             (setq rlt (evil-filepath-get-path-already-inside)))
        ;; maybe (point) is in the middle of the path
        t
      ;; need search forward AND backward to find the right path
      (save-excursion
        ;; path in backward direction
        (when (setq b (evil-filepath-search-forward-char 'evil-filepath-is-separator-char t))
          (goto-char b)
          (setq f1 (evil-filepath-get-path-already-inside))))
      (save-excursion
        ;; path in forward direction
        (when (setq b (evil-filepath-search-forward-char 'evil-filepath-is-separator-char))
          (goto-char b)
          (setq f2 (evil-filepath-get-path-already-inside))))
      ;; pick one path as the final result
      (cond
       ((and f1 f2)
        (if (> (- (point) (nth 2 f1)) (- (nth 0 f2) (point)))
            (setq rlt f2)
          (setq rlt f1)))
       (f1
        (setq rlt f1))
       (f2
        (setq rlt f2))))

    rlt))

(evil-define-text-object evil-filepath-inner-text-object (&optional count begin end type)
  "File name of nearby path"
  (let ((selected-region (evil-filepath-extract-region)))
    (if selected-region
        (evil-range (nth 1 selected-region) (nth 2 selected-region) :expanded t))))

(evil-define-text-object evil-filepath-outer-text-object (&optional NUM begin end type)
  "Nearby path"
  (let ((selected-region (evil-filepath-extract-region)))
    (if selected-region
        (evil-range (car selected-region) (+ 1 (nth 2 selected-region)) type :expanded t))))

(define-key evil-inner-text-objects-map "f" 'evil-filepath-inner-text-object)
(define-key evil-outer-text-objects-map "f" 'evil-filepath-outer-text-object)
;; }}

柬埔寨名汤(Sgow Chrouk Soup)

视频教程: https://www.youtube.com/watch?v=Aclyh2c_BEs

1 备料

  • 两人份冷水
  • 米少许(半茶勺)
  • 香茅一根
  • 南姜(Galangal)一小块
  • 洋葱一个
  • 大蒜二瓣
  • 鸡精二茶勺
  • 糖(最好棕榈糖,因为有香味)半茶勺
  • 九层塔(薄荷)一把
  • 鲜红辣椒两到三根

Sgow-Chrouk-Soup-prepare.jpg

2 步骤

  • 鸡胸脯肉切成小块,盐过一下三四分钟,洗净刀和板
  • 烧开水
  • 香茅洗净,折成多段.南姜先去皮再洗净切成薄片.米洗净,大蒜拍扁,洋葱切好
  • 待水滚放入香茅,南姜,米,大蒜,洋葱(增加甜味)
  • 在水再次滚前,摘薄荷叶,放入大碗淘洗多次再沥干,切成细丝(让香味挥发).辣椒洗净切碎.柠檬切成长条.前述三种料分门别类放入一盘中.
  • 选部份柠檬挤汁入一小碟子中
  • 这时水已滚,加鸡精调味至满意,放入鸡肉(海鲜也可以):
  • 一分钟水滚后,尝一下鸡肉是否熟了.汤的口味,加入糖(略有甜味即可),如果口味淡了,可最后加入鸡精
  • 关火,撒入少量辣椒末,薄荷,葱

3 上菜

  • 准备大碗,放入薄荷,葱,适量柠檬汁,倒汤
  • 柬埔寨神秘香菜(可选)
  • 可加少量鱼露调咸味

Sgow-Chrouk-Soup-done.jpg

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.