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:

  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


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.

Migrate blog from wordpress into nikola

UPDATED: <2015-01-31 Sat>

CREATED: <2014-04-22>

Here are the steps. Tested on Cygwin/Mac/Linux.

1 Requirements

# install python2 and make sure sqlite is supported
sudo USE="sqlite" emerge -a =python-2* # Gentoo Linux

Make sure gcc libxslt-devel libxml2-devel zlib-devel installed, especially on cygwin. See http://getnikola.com/handbook.html#libxml-libxslt-files-missing-errors.

2 Install

2.1 Install Nikola

Please note the package "requests" is required by zen theme, so it's optional.

I prefer install packages into my HOME directory because it versatile and safe.

For example, "sudo pip install" could screw up python on Gentoo Linux.

# @see http://stackoverflow.com/questions/2915471/install-a-python-package-into-a-different-directory-using-pip
pip install --user markdown webassets nikola requests
export PATH=$PATH:$HOME/.local/bin

2.2 Install theme (OPTIONAL)

I use zen theme,

rm -rf themes/zen/;nikola install_theme zen
# or rm -rf themes/zen/; http_proxy= nikola install_theme zen at mainland China

Backup you theme under "~/.config/nikola/themes/zen". You won't need above command any more.

3 Usage

3.1 Create root directory

mkdir -p ~/.config/;nikola init "nikola";cd ~/.config/nikola;

Please note `nikola init "nikola"` creates "conf.py" for you. Backup this file and you never need `nikola init`.

3.2 Import old posts and build new site

nicola import_wordpress my_wordpress_dump.xml
nikola build

The website is create at "~/.config/nikola/output".


4.1 If wordpress use 3rd parth JS to render code

Use below command to fix embedded code in HTML files:

find -name '*.wp' -exec grep -l "\[sourcecode.*\<diff\>.*\]" {} \; |xargs sed -i 's/\[sourcecode.*\<diff\>.*\]/<pre class="brush: diff;">/g
find -name '*.wp' -exec grep -l "~~~~~~~~~~~~" {} \;|xargs sed -i "s%~~~~~~~~~~~~%</pre>%g"

Manually fixed those articles with Chinese titles in url_map.csv

4.2 Import comments into Disqus

Disqus is a blog comment hosting service.

Use below script to fixed the comment dumped from wordpress before importing it into Disqus:

import getopt, sys, csv
def usage():
    print '''
    fix url mapping when migrate wordpress blog into nikola
    python fix-url-map.py [options]

if __name__ == '__main__':
        opts, args = getopt.getopt(sys.argv[1:], "hf:x:", ["help", "file=","xml="])
    except getopt.GetoptError as err:
        # print help information and exit:
        print str(err) # will print something like "option -a not recognized"


    for o, a in opts:
        if o in ("-h", "--help"):
        elif o in ("-f", "--file"):
            file= a
        elif o in ("-x", "--xml"):
            assert False, "unhandled option"

    with open(xml, 'r') as content_file:
        content = content_file.read()

    with open(file, 'rb') as csvfile:
         spamreader = csv.reader(csvfile, delimiter=',')
         for row in spamreader:

    print content

4.3 Setup 3rd party JS libraries on new site

Install syntaxhighlighter and google analytics, insert below code into conf.py:

BODY_END = """
<script type='text/javascript' src='//cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shCore.min.js'></script>
<script type='text/javascript' src='//cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/shAutoloader.min.js'></script>
<script type='text/javascript'>
function path()
  var args = arguments,
      result = [];

  for(var i = 0; i < args.length; i++){
    result.push(args[i].replace('@', '//cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/scripts/'));

  return result;

SyntaxHighlighter.autoloader.apply(null, path(
  'applescript            @shBrushAppleScript.min.js',
  'actionscript3 as3      @shBrushAS3.min.js',
  'bash shell             @shBrushBash.min.js',
  'coldfusion cf          @shBrushColdFusion.min.js',
  'cpp c c++              @shBrushCpp.min.js',
  'clojure                @shBrushScala.min.js',
  'c# c-sharp csharp      @shBrushCSharp.min.js',
  'css                    @shBrushCss.min.js',
  'delphi pascal          @shBrushDelphi.min.js',
  'diff patch pas         @shBrushDiff.min.js',
  'erl erlang             @shBrushErlang.min.js',
  'groovy                 @shBrushGroovy.min.js',
  'java                   @shBrushJava.min.js',
  'jfx javafx             @shBrushJavaFX.min.js',
  'js jscript javascript  @shBrushJScript.min.js',
  'perl pl                @shBrushPerl.min.js',
  'php                    @shBrushPhp.min.js',
  'text plain             @shBrushPlain.min.js',
  'py python              @shBrushPython.min.js',
  'ruby rails ror rb      @shBrushRuby.min.js',
  'sass scss              @shBrushSass.min.js',
  'scala                  @shBrushScala.min.js',
  'sql                    @shBrushSql.min.js',
  'vb vbnet               @shBrushVb.min.js',
  'xml xhtml xslt html    @shBrushXml.min.js'
<script type='text/javascript'>
var _gaq = _gaq || [];

_gaq.push(['_setAccount', 'UA-29850823-2']);
_gaq.push(['_addDevId', 'i9k95']); // Google Analyticator App ID with Google

(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);

<link rel='stylesheet' type='text/css' href='//cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCore.min.css'>
<link rel='stylesheet' type='text/css' href='//cdnjs.cloudflare.com/ajax/libs/SyntaxHighlighter/3.0.83/styles/shCoreEmacs.css'>

In above code, shCoreEmacs.css is the color theme for syntaxhighlighter.

4.4 Upload

I use FileZilla as a FTP client. NcFTP is a command line alternative.

The best way to upload HTML files is Git.

What's the best spell check set up in emacs

To save your time, I started from conclusion.

1 Suggestion for non-programmers

Emacs will find the right dictionary by querying your locale.

Run command "locale" in your shell to get current locale.

If you want to force Emacs use dictionary "en_US", copy below code into your ~/.emacs:

;; find aspell and hunspell automatically
 ((executable-find "aspell")
  (setq ispell-program-name "aspell")
  (setq ispell-extra-args '("--sug-mode=ultra" "--lang=en_US")))
 ((executable-find "hunspell")
  (setq ispell-program-name "hunspell")
  (setq ispell-extra-args '("-d en_US")))

That's it!

Please run command "man aspell" or "man hunspell" in shell if you have more questions. I've nothing more to say.

2 Suggestion for programmers

I strongly recommend aspell instead of hunspell (Though hunspell is fine).

Please insert below code into your ~/.emacs:

;; if (aspell installed) { use aspell}
;; else if (hunspell installed) { use hunspell }
;; whatever spell checker I use, I always use English dictionary
;; I prefer use aspell because:
;; 1. aspell is older
;; 2. looks Kevin Atkinson still get some road map for aspell:
;; @see http://lists.gnu.org/archive/html/aspell-announce/2011-09/msg00000.html
(defun flyspell-detect-ispell-args (&optional RUN-TOGETHER)
  "if RUN-TOGETHER is true, spell check the CamelCase words"
  (let (args)
     ((string-match  "aspell$" ispell-program-name)
      ;; force the English dictionary, support Camel Case spelling check (tested with aspell 0.6)
      (setq args (list "--sug-mode=ultra" "--lang=en_US"))
      (if RUN-TOGETHER
          (setq args (append args '("--run-together" "--run-together-limit=5" "--run-together-min=2")))))
     ((string-match "hunspell$" ispell-program-name)
      (setq args nil)))

 ((executable-find "aspell")
  (setq ispell-program-name "aspell"))
 ((executable-find "hunspell")
  (setq ispell-program-name "hunspell")
  ;; just reset dictionary to the safe one "en_US" for hunspell.
  ;; if we need use different dictionary, we specify it in command line arguments
  (setq ispell-local-dictionary "en_US")
  (setq ispell-local-dictionary-alist
        '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil nil nil utf-8))))
 (t (setq ispell-program-name nil)))

;; ispell-cmd-args is useless, it's the list of *extra* arguments we will append to the ispell process when "ispell-word" is called.
;; ispell-extra-args is the command arguments which will *always* be used when start ispell process
(setq ispell-extra-args (flyspell-detect-ispell-args t))
;; (setq ispell-cmd-args (flyspell-detect-ispell-args))
(defadvice ispell-word (around my-ispell-word activate)
  (let ((old-ispell-extra-args ispell-extra-args))
    (ispell-kill-ispell t)
    (setq ispell-extra-args (flyspell-detect-ispell-args))
    (setq ispell-extra-args old-ispell-extra-args)
    (ispell-kill-ispell t)

That's it.

3 Why

3.1 Aspell

apell is recommended because its option "–run-together". That option could spell check the camel case word. Variable name often uses camel case naming convention these days. Read my Effective spell check in Emacs for advanced tips on how to use flyspell to check variable names.

If Emacs start a aspell process with "–run-together" option, that process is not closed so it can be re-used by other commands.

This behaviour will be a problem if you want to let Emacs/aspell correct the typo by running the command "ispell-word" because a apell process with "–run-together" will produce much noise.

For example, for a typo "helle" Emacs will give you too many candidates. It's hard to find the desired word "hello": aspell-camelcase-suggest-nq8.png

The better solution is before running "M-x ispell-word", we'd better restart a aspell proces without the argument "–run-together".

Here is the screen shot after we applying this fix: aspell-normal-suggest-nq8.png

As I mentioned, the global variable "ispell-extra-args" contains arguments Emacs will always append to a spell checker process (aspell or hunspell). That's the only variable you need care about.

There is another variable named "ispell-cmd-args". It is actually some extra arguments Emacs could send to an existing spell checker process when you "M-x ispell-word". In my opinion, it's useless. I mention it because the naming are really confusing. "ispell-extra-args" is actually command line arguments the spell checker will always use. The "ispell-cmd-args" are actually the extra arguments will be used in certain cases.

3.2 Hunspell

I cannot find hunspell option to check camel case words. Please enlighten me if you know the option.

Hunspell has some design flaw. It will always check the environment variable LC_ALL, LC_MESSAGES and LANG at first to find the default dictionary unless you specify the dictionary in the command line. If it cannot find the default dictionary, the spell checker process won't start. Aspell does not have this issue, if it cannot find the zh_CN dictionary, it will fall back into English.

Specify the ispell-extra-args won't stop hunspell to search for the default dictionary at the beginning.

For example, I am a Chinese and my locale is "zh_CN.utf-8". So hunspell will always search the dictionary zh_CN. Even I'm only interested in English spell checking.

To specify the dictionary explicitly, I need hack the Emacs code which is kind of mess. Finally, I figure out. It's just several lines of elisp code:

(setq ispell-program-name "hunspell")
(setq ispell-local-dictionary "en_US")
(setq ispell-local-dictionary-alist
      '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil nil nil utf-8)))

How to embed button in wxDataViewListCtrl

Before creating wxDataViewListCtrl, it's better to detect information about text size so that we can set the column width and height intelligently.

Here is the code:

void GuessRowSize(int* w, int* h) {
    //@see http://sourceforge.net/apps/trac/codelite/browser/trunk/LiteEditor/new_build_tab.cpp?rev=5804

// Determine the row height
    wxBitmap tmpBmp(1, 1);
    wxMemoryDC memDc;
    wxFont f = wxSystemSettings::GetFont(wxSYS_ANSI_FIXED_FONT);
    int xx, yy;
    memDc.GetTextExtent(wxT("Tp"), &xx, &yy, NULL, NULL, &f);

    //enough height for ICON
    *h=yy<16? 16: yy;

    memDc.GetTextExtent("Wp", &xx, &yy, NULL, NULL, &f);

wxDataViewListCtrl* ctrl=new wxDataViewListCtrl(parent,-1);
int w,h;
//hard code width may not be good, may be can use w ,h
AppendTextColumn(_T("Column 1"),wxDATAVIEW_CELL_INERT,200 /*width*/);
//must be called after Create()

// well I need provide button render by myself
class wxDataViewMyButtonRenderer: public wxDataViewCustomRenderer, public wxTimer
    wxDataViewMyButtonRenderer( const wxString &varianttype = wxT("wxString"),
                              int align = wxDVR_DEFAULT_ALIGNMENT );

    virtual bool SetValue( const wxVariant &value );
    virtual bool GetValue( wxVariant &value ) const;

    virtual bool Render( wxRect, wxDC*, int);
    virtual wxSize GetSize() const;
    virtual void Notify();
    void SetParent(GalleryListView*);
    // Implementation only, don't use nor override
    virtual bool ActivateCell(const wxRect& rect,
        wxDataViewModel *model,
        const wxDataViewItem& item,
        unsigned int col,
        const wxMouseEvent *mouseEvent);

    wxString m_value;
    bool m_button_clicked;
    wxRect m_cell_rect;
    GalleryListView* m_parent;


When user click one row, the ActivateCell is called, we can use some rect detect algorithm to find if the button rect is clicked.

There is no mouse up event handler! So I have to hack, basically start a wxTimer when button clicked and draw the button up effect after about 1 second in Notify(). That's why I ask wxDataViewMyButtonRenderer to inherit from wxTimer.

To draw the button up effect, I need ask the parent wxDataViewListCtrl to refresh itself, so that the button's Render() method has a chance to be called. That's why we need SetParent().

Here is the part of implementation:

wxDataViewMyButtonRenderer::ActivateCell(const wxRect& rect,
    wxDataViewModel *model,
    const wxDataViewItem& item,
    unsigned int col,
    const wxMouseEvent *mouseEvent)
    wxDataViewListStore* store=(wxDataViewListStore*) model;

    if ( mouseEvent ) {
        if ( !wxRect(GetSize()).Contains(mouseEvent->GetPosition()) ){
            return false;
        wxVariant item_value_pdf;
        store->GetValueByRow(item_value_pdf , store->GetRow(item),0);
        wxVariant item_value_created;
        store->GetValueByRow(item_value_created , store->GetRow(item),1);

    } else {
        wxLogDebug(_T("Sorry, I don't handle keyboard"));

    return true;

wxDataViewMyButtonRenderer::Render( wxRect rect, wxDC *dc, int state )
    wxLogDebug(_T("Render called"));
    // Ensure that the check boxes always have at least the minimal required
    // size, otherwise DrawCheckBox() doesn't really work well. If this size is
    // greater than the rect size, the checkbox will be truncated but this is a
    // lesser evil.
    wxSize size = rect.GetSize();

    // draw button
    if(m_button_clicked==true && rect.Intersects(m_cell_rect)){
        //draw the button when clicked
        dc->SetBrush(wxBrush(wxColour(65, 150, 65), wxBRUSHSTYLE_SOLID));
        dc->SetPen( *wxBLACK_PEN );

        // draw button push down effect. since we cannot detect mouse up event, we use
        // timer to draw it
        const int time_to_button_up=350;
    } else {
        // draw normal button
        dc->SetBrush(wxBrush(wxColour(84, 174, 84), wxBRUSHSTYLE_SOLID));
        dc->SetPen( *wxBLACK_PEN );

        //draw inner white border
        dc->SetPen( *wxWHITE_PEN );


    return true;

popup function where your cursor is in

Which Func Mode will display the current function name in the mode line.

But I can't use it.

It's because I reduce my mode-line into minimum to make it work with my tiny netbook.

Showing the function in header is also not possible because it conflicts with my color theme.

So I choose to display the function name in a popup.

Insert below code into ~/.emacs,

(autoload 'which-function "which-func")
(autoload 'popup-tip "popup")

(defun copy-yank-str (msg)
  (kill-new msg)
    (insert msg)
    (shell-command-on-region (point-min) (point-max)
                              ((eq system-type 'cygwin) "putclip")
                              ((eq system-type 'darwin) "pbcopy")
                              (t "xsel -ib")

(defun popup-which-function ()
  (let ((msg (which-function)))
    (popup-tip msg)
    (copy-yank-str msg)

The extra bonus is "popup-which-function" will also insert the function name into clipboard and kill ring.

The above code need package "popup.el" to be installed.

Screen shot: pop-which-func-nq8.png #+image/pop-which-func-nq8.png wpid-pop-which-func-nq8.png

paste string from clipboard into minibuffer in Emacs

First, I find "Alt-Y" is easier to press when paste string into minibuffer than old "Shift+Insert". "Ctrl-V" is not supported in some terminals.

Second, intergrating some command line clipboard tool into Emacs is better than Emacs X clipboard. Command line tool works in any environment. For example, when you log into a remote server with ssh, your local computer could share clipboard with your Emacs in remote shell. Please note in this example, you need using X11 forward over ssh, usually it's already set up on server.

Here is the code:

(defun paste-from-x-clipboard()
    (*cygwin* "getclip")
    (*is-a-mac* "pbpaste")
    (t "xsel -ob")

(defun my/paste-in-minibuffer ()
  (local-set-key (kbd "M-y") 'paste-from-x-clipboard)

(add-hook 'minibuffer-setup-hook 'my/paste-in-minibuffer)

Upload emacs package to marmalade

Thanks to Sebastian Wiesner for providing a python script to upload package to http://marmalade-repo.org/.

sudo pip install requests keyring

curl -L https://raw.githubusercontent.com/lunaryorn/dotfiles/master/emacs/bin/marmalade-upload > ~/bin/marmalade-upload && chmod +x ~/bin/marmalade-upload

~/bin/marmalade-upload -h # see help

Please note if the same version already exists on server, I need delete it before uploading.

Use firefox in Emacs way

UPDATED: <2014-05-20 Tue>

CREATED: <2014-03-23 Sun>

I use keysnail. it's a firefox addon to convert firefox into Emacs.

I install keysnail's own plugins "HoK" and "Tanything".

Here are the use cases of keysnail.

1 Click & Copy the links

Install the HoK.

Insert below code into ~/.keysnail.js:

hook.setHook("PluginLoaded", function () {
    if (!plugins.hok) return;

    /* HoK 1.3.9+ required */
    plugins.hok.pOptions.selector = plugins.hok.pOptions.selector
    /* feedly */
    + ", *[data-uri]" + ", *[data-selector-toggle]" + ", *[data-page-action]" + ", *[data-app-action]"
    /* google plus */
    + ", *[guidedhelpid]"
    /* twitter */
    + ", *[data-item-count]";

key.setGlobalKey(["C-c","C-f"], function (aEvent, aArg) {
        ext.exec("hok-start-foreground-mode", aArg);
}, "Hok - Foreground hint mode", true);

key.setGlobalKey(["C-c","C-b"], function (aEvent, aArg) {
        ext.exec("hok-start-background-mode", aArg);
}, "HoK - Background hint mode", true);

key.setGlobalKey(["C-c","C-y"], function (aEvent, aArg) {
        ext.exec("hok-yank-foreground-mode", aArg);
}, "HoK - Background hint mode", true);

key.setGlobalKey(["C-c","C-;"], function (aEvent, aArg) {
        ext.exec("hok-start-extended-mode", aArg);
}, "HoK - Extented hint mode", true);

key.setGlobalKey(["C-c", "C-e"], function (aEvent, aArg) {
        ext.exec("hok-start-continuous-mode", aArg);
}, "Start continuous HaH", true);

Now I can press "Ctrl-C Ctrl-F" to select the links to click.

The links will be highlighted as below screen shot: keysnail-hok-nq8.png

I can press the highlighted hint to click the link.

Copy the link into clipboard is also easy. I press "C-c C-y" and similar UI will be displayed. I press the hint above to finish the operation.

2 Copy the text of link

This is my most favorite tool. As a developer, I need copy text of links from bug tracking software and paste it into git commit message frequently.

That's error prone because:

  1. The links are cluttered so it's hard to select the text of the link without actually clicking the link.
  2. The web UI of bug tracing system may use some CSS magic. Only part of the text of links are displayed. So it's impossible to select the text at all. For example, JIRA will display file name of "Screenshot 03/05/2014-193024.png" as "Screenshot 03/05/2014…png".

If you use keysnail+HOK, it's as simple as press hot key "C-c ; Y". That's it!

3 Switch tab

Install the Tanything.

Insert below code into ~/.keysnail.js:

key.setGlobalKey(["C-c", "C-a"], function (ev, arg) {
  ext.exec("tanything", arg);
}, "view all tabs", true);

Press "Ctrl-C Ctrl-A", you got below UI: keysnail-tanything-nq8.png

Tanything will locate the tab by finding match in web page's title or URL.

4 My keysnail set up

You can download my ~/.keysnail.js from HERE.

#+image/keysnail-hok-nq8.png wpid-keysnail-hok-nq8.png #+image/keysnail-tanything-nq8.png keysnail-tanything-nq8.png

Debug Emacs Lisp code the hard way

I met some issue when using helm several days ago.

With the help from Michael Heerdegen, I can locate the line error message is dumped.

But the problem is I cannot get the backtrace at all.

So I insert some backtrace printing code above that line and "M-x eval-buffer":

(with-output-to-temp-buffer "backtrace-output"
  (let ((var 1))
      (setq var (eval '(progn
                         (1+ var)
                         (list 'testing (backtrace))))))))

It turns out that some third party package I installed changes the emacs variable "display-buffer-function". I guess that's why the backtrace is blocked. Because backtrace is usually dumped into a backtrace buffer. And backtrace buffer's is actually influenced by the variable.

How to use ctags in Emacs effectively

Exuberant Ctags is a code navigation tool. It supports many language and could be integrated into Emacs well.

Please read EmacsWiki for basic usage.

I will talk about how I manage my ctags.

Basically ctags will produce a index file with file name TAGS. The full path of TAGS will be stored in a global list "tags-table-list".

An example of tags-table-list:

(setq tags-table-list '("~/wxWidgets-master/TAGS" "~/projs/Loris/src/desktop/TAGS"))

Every time we you "M-x find-tag", the TAGS file in above list will be read from the scratch to locate the definition of the symbol under cursor.

Here is my strategy to manage TAGS automatically:

  • I hard coded full path of TAGS in .emacs because I usually don't change project path.
  • In major mode hook like c++-mode-hook or js2-mode-hook I will check the directory path of current file. If it contains certain string, I suppose the file belong to certain project.
  • Then I will create TAGS for that project if needed
  • Every time when I save the file, I may update TAGS according to the value of tags-table-list.

Here is the code:

(defun my-project-name-contains-substring (REGEX)
  (let ((dir (if (buffer-file-name)
                 (file-name-directory (buffer-file-name))
    (string-match-p REGEX dir)))

(defun my-create-tags-if-needed (SRC-DIR &optional FORCE)
  "return the full path of tags file"
  (let ((dir (file-name-as-directory (file-truename SRC-DIR)) )
    (setq file (concat dir "TAGS"))
    (when (or FORCE (not (file-exists-p file)))
      (message "Creating TAGS in %s ..." dir)
       (format "ctags -f %s -e -R %s" file dir))

(defvar my-tags-updated-time nil)

(defun my-update-tags ()
  "check the tags in tags-table-list and re-create it"
  (dolist (tag tags-table-list)
    (my-create-tags-if-needed (file-name-directory tag) t)

(defun my-auto-update-tags-when-save ()
   ((not my-tags-updated-time)
    (setq my-tags-updated-time (current-time)))
   ((< (- (float-time (current-time)) (float-time my-tags-updated-time)) 300)
    ;; < 300 seconds
    ;; do nothing
    (setq my-tags-updated-time (current-time))
    (message "updated tags after %d seconds." (- (float-time (current-time))  (float-time my-tags-updated-time)))

(defun my-setup-develop-environment ()
    (when (my-project-name-contains-substring "Loris")
       ((my-project-name-contains-substring "src/desktop")
        ;; C++ project don't need html tags
        (setq tags-table-list (list
                                (concat (file-name-as-directory (getenv "WXWIN")) "include"))
                               (my-create-tags-if-needed "~/projs/Loris/loris/src/desktop")))
       ((my-project-name-contains-substring "src/html")
        ;; html project donot need C++ tags
        (setq tags-table-list (list (my-create-tags-if-needed "~/projs/Loris/loris/src/html")))

(add-hook 'after-save-hook 'my-auto-update-tags-when-save)
(add-hook 'js2-mode-hook 'my-setup-develop-environment)
(add-hook 'web-mode-hook 'my-setup-develop-environment)
(add-hook 'c++-mode-hook 'my-setup-develop-environment)
(add-hook 'c-mode-hook 'my-setup-develop-environment)

UPDATE: There is some discussion at Google Plus about using ctags. Kaushal Modi recommended three emacs plugins:

  1. ctags-update
  2. etags-table
  3. etags-select

I tried these three plugins. ctags-update and etags-table duplicate my above elisp code. I prefer my own code because it's simpler and totally controllable. For example, the fact that I need only care about only one global variable tags-table-list makes my code shorter.

But I do like etags-select, it provide better UI for finding tag and I will use it from now on.