Easy indentation setup in Emacs for web development

indentation setup in Emacs for web development :en:emacs:web:indent:

CREATED: <2015-04-02 Thu>

UPDATE: <2016-06-22 Wed>

This question has been asked so many times, so I answered it here once for all.

Solution

Insert below code into ~/.emacs:

(defun my-setup-indent (n)
  ;; java/c/c++
  (setq-local c-basic-offset n)
  ;; web development
  (setq-local coffee-tab-width n) ; coffeescript
  (setq-local javascript-indent-level n) ; javascript-mode
  (setq-local js-indent-level n) ; js-mode
  (setq-local js2-basic-offset n) ; js2-mode, in latest js2-mode, it's alias of js-indent-level
  (setq-local web-mode-markup-indent-offset n) ; web-mode, html tag in html file
  (setq-local web-mode-css-indent-offset n) ; web-mode, css in html file
  (setq-local web-mode-code-indent-offset n) ; web-mode, js code in html file
  (setq-local css-indent-offset n) ; css-mode
  )

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

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

(defun my-setup-develop-environment ()
  (interactive)
  (let ((proj-dir (file-name-directory (buffer-file-name))))
    ;; if hobby project path contains string "hobby-proj1"
    (if (string-match-p "hobby-proj1" proj-dir)
        (my-personal-code-style))

    ;; if commericial project path contains string "commerical-proj"
    (if (string-match-p "commerical-proj" proj-dir)
        (my-office-code-style))))

;; prog-mode-hook requires emacs24+
(add-hook 'prog-mode-hook 'my-setup-develop-environment)
;; a few major-modes does NOT inherited from prog-mode
(add-hook 'lua-mode-hook 'my-setup-develop-environment)
(add-hook 'web-mode-hook 'my-setup-develop-environment)

You can use `prog-mode-hook` and `buffer-file-name` to do the per project setup easily.

For any new programming language not covered in this post, there are two steps:

  • Know your major-mode by pressing `ALT-X major-mode ENTER`. Say you got "org-mode".
  • Google "Emacs org-mode indent offset"

Tips (OPTIONAL)

  • Please note the first paramter of `string-match-p` is actually regular expression. So you can write regular expression like "\\(/proj1\\|work.org\\)"
  • Emacs gives you full freedom to setup project. If you prefer per computer setup instead of per project setup, you only need re-write `my-setup-develop-environment',
(defun my-setup-develop-environment ()
  (interactive)
  (let ((hostname (with-temp-buffer
                    (shell-command "hostname" t)
                    (goto-char (point-max))
                    (delete-char -1)
                    (buffer-string))))

  (if (string-match-p "home-pc" hostname)
      (my-personal-code-style))

  (if (string-match-p "office-pc" hostname)
      (my-office-code-style))))
  • Instead of press TAB manually to indent code line by line, you can select a region and press `Alt-x indent-region ENTER`
  • Because I use Evil, indenting the whole file is as simple as pressing `gg=G`

Effective code navigation for web development

code navigation for web development :en:javascript:emacs:ctags:

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

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.

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.

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".

Sublime Text

Install CTags plugin and follow its instruction.

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

with a dictionary and hippie-expand :en:emacs:dictionary:autocomplete:

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.

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))

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.

Demo blog post created by nikola&org2nikola

blog post created by nikola&org2nikola :en:emacs:org2nikola:nikola:blog:

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

How to validate HTML5 code with Flymake effectively

to validate HTML5 code with Flymake effectively :en:emacs:html:tidy:validation:

UPDATED: <2014-12-11 Thu>

CREATED: <2014-12-05 Fri>

Here is the solution from EmacsWiki.

I found it not effective because tidy is too strict to HTML5 and produces too much noise.

I only need validation of missing open/closed html tag(s). Nothing more!

So here is my complete solution:

(defun flymake-html-init ()
       (let* ((temp-file (flymake-init-create-temp-buffer-copy
                          'flymake-create-temp-inplace))
              (local-file (file-relative-name
                           temp-file
                           (file-name-directory buffer-file-name))))
         (list "tidy" (list local-file))))

(defun flymake-html-load ()
  (interactive)
  (when (and (not (null buffer-file-name)) (file-writable-p buffer-file-name))
    (set (make-local-variable 'flymake-allowed-file-name-masks)
         '(("\\.html\\|\\.ctp\\|\\.ftl\\|\\.jsp\\|\\.php\\|\\.erb\\|\\.rhtml" flymake-html-init))
         )
    (set (make-local-variable 'flymake-err-line-patterns)
         ;; only validate missing html tags
         '(("line \\([0-9]+\\) column \\([0-9]+\\) - \\(Warning\\|Error\\): \\(missing <\/[a-z0-9A-Z]+>.*\\|discarding unexpected.*\\)" nil 1 2 4))
         )
    (flymake-mode t)))

(add-hook 'web-mode-hook 'flymake-html-load)
(add-hook 'html-mode-hook 'flymake-html-load)
(add-hook 'nxml-mode-hook 'flymake-html-load)
(add-hook 'php-mode-hook 'flymake-html-load)

The only difference from EmacsWiki is only one line:

'(("line \\([0-9]+\\) column \\([0-9]+\\) - \\(Warning\\|Error\\): \\(missing <\/[a-z0-9A-Z]+>.*\\|discarding unexpected.*\\)" nil 1 2 4))

Advanced tip on using mozrepl to automatically refresh browser

tip on using mozrepl to automatically refresh browser :en:emacs:firefox:mozrepl:

Here is the setup on Emacswiki.

I find it annoying instead of helpful in real world usage.

The mozrepl trick basically will refresh the current active page in Firefox. It's not smart enough. When developing a web application, I will open many stackoverflow pages to look for technical solution. I don't like my stackoverflow page being refreshed when I'm saving the HTML code for some web application.

So here is my improved solution:

(defun my-moz-refresh-browser-condition (current-file)
  "Should return a boolean javascript expression or nil"
  (let (rlt)
    (cond
     ((string-match "\\(beeta\\|cb_tutorial\\)" current-file)
      (setq rlt "content.document.location.href.indexOf(':8001')!==-1"))
     (t
      (setq rlt nil)))
    rlt))

;; {{ mozrepl auto-refresh browser
(defun moz-reload-browser ()
  (interactive)
  (let (js-cond cmd)
    (if (fboundp 'my-moz-refresh-browser-condition)
        (setq js-cond (funcall 'my-moz-refresh-browser-condition (buffer-file-name))))
    (cond
     (js-cond
      (setq cmd (concat "if(" js-cond "){setTimeout(function(){content.document.location.reload(true);}, '500');}")))
     (t
      (setq cmd "setTimeout(function(){content.document.location.reload(true);}, '500');")))
    (comint-send-string (inferior-moz-process) cmd)
    ))

(defun moz-after-save ()
  (interactive)
  (when (memq major-mode '(web-mode html-mode nxml-mode nxhml-mode php-mode))
    (moz-reload-browser)))

(add-hook 'after-save-hook
              'moz-after-save
              'append 'local)
;; }}

Tips on using Ctags with Emacs

on using Ctags with Emacs :en:emacs:ctags:javascript:

Ctags is critical to my web projects. I use it for code navigation by `M-x find-tag` and code auto-completion by using company-etags from company-mode.

The first tip is to use global variable `tags-table-list` instead of `tags-file-name`. The Emacs documentation says you should NOT set both. `tags-table-list` is better because it's a list, where you can store multiple tag files.

Here is the value of `tags-table-list` for one project:

("/Users/cb/projs/their-project/test/cdn/test/assets/test/js/TAGS" "/Users/cb/projs/their-project/test/app/TAGS")

The purpose to use multiple tag files in sub-folders instead of one tag file in root folder is to scan less code files.

The second tip is we can avoid feeding big js files to ctags. Currently one of my client's project is not managed well. They place the concatenated js files, third party js libraries, and normal code file into one folder. The naming of files is a mess. So I can not tell which is which from file name or file path. The tag file created from those big concatenated js files will crash my Emacs.

Change the ctags command line underhood will solve the problem. Here is the actual liner to create a tag file:

find proj-dir -type f -not -iwholename '*TAGS' -not -size +16k | ctags -f ~/proj/output/TAGS -e -L -

The point is the option `-not -size +16k`. It means only handle files less thank 16k.

Here is the Emacs lisp function to wrap above shell command:

(defun my-create-tags-if-needed (SRC-DIR CTAGS-OPTS &optional FORCE)
  "return the full path of tags file"
  ;; TODO save the CTAGS-OPTS into hash
  (let ((dir (file-name-as-directory (file-truename SRC-DIR)) )
       file
       cmd)
    (setq file (concat dir "TAGS"))
    (when (or FORCE (not (file-exists-p file)))
      (setq cmd (format "find %s -type f -not -iwholename '*TAGS' -not -size +24k | ctags -f %s -e  %s -L -" dir file CTAGS-OPTS))
      (shell-command cmd))
    file))

BTW, here is my ~/.ctags.

In summary, there are ONLY two points:

  • What's the `tags-table-list`
  • How the tags file are created by ctags

UPDATE:

  1. My tags file management strategy is described at How to use ctags in Emacs effectively. It's effective to me. But it may be not generic enough to apply to others' use cases.
  2. I do use Gnu Global for C/C++/Java code. I use it exactly in the same way as ctags. Please `man global` for the details. Hint, all you need care is the environment variable GTAGSLIBPATH.

Debug efficiently in Emacs

efficiently in Emacs :en:javascript:emacs:

Please note:

  • I use javascript as a use case. The solution is generic enough to be applied to any language. At the end of the article, Emacs Lisp is used as another example.
  • I'm good at most debuggers. The point of this article is to deal with more difficult issues the debugger can not handle.
  • My code quality is fine. It handles all the corner cases I've met. For example, single quote and double quotes will be escaped properly.

Problem

As a freelancer I am often required to debug legacy javascript code from some huge "enterprise"" applications.

The only way to debug such application is to insert as many as possible logging code, watch the output, and think.

So my problem is how to insert logging code as quickly as possible.

Solution

My solution is Yasnippet. Yasnippet allow me to insert executable Emacs Lisp code in its snippet. I will Sotake full advantage of that feature.

Logging simple JS variable

Given a variable name like "var1", I need insert javascript code as below:

console.log("var1=", var1)

Snippet: https://github.com/redguardtoo/emacs.d/blob/master/snippets/js-mode/logobject.yasnippet

This snippet need you input variable name once.

Logging complex JS variable

In real world, the JS variable is often a string like "MyController.servicea.find('.class').attr1" which I hate to type.

So the solution is that I copy the JS variable into kill ring where my snippet will read the variable name.

Snippet: https://github.com/redguardtoo/emacs.d/blob/master/snippets/js-mode/log-recent-kill-ring.yasnippet

Logging JS function name when it's called

In below example, I need insert "console.log('hello is called');" in function "hello":

function hello() {
  console.log('hello is called');
}

Snippet: https://github.com/redguardtoo/emacs.d/blob/master/snippets/js-mode/log-which-function.yasnippet

This snippet use the Emacs command `which-function`. If you read the code of `which-function`, you will find that it's Imenu-mode who actually extracts the function name. But Imenu-mode requires you pre-define enough regular expressions.

Please check https://github.com/redguardtoo/emacs.d/blob/master/lisp/init-javascript.el. I have defined many regular expressions for AngularJS and JQuery.

The regular expressions could be used in both js-mode and js2-mode.

Logging JS function name with its parameters

JS example:

function hello(v1, v2,
               v3, v4) {
  console.log("v1=", v1, "v2=", v2, "v3=", v3, "v4=", v4);
}

I copy the parameter of JS function named "hello". It's just the content between "hello(" and ")", then Emacs Lisp code embedded in the snippet will parse and output the right content from kill ring.

Snippet: https://github.com/redguardtoo/emacs.d/blob/master/snippets/js-mode/log-which-function-with-para.yasnippet

BTW, since I use Evil, copy the parameter into kill-ring is as simple as pressing "yi(".

Insert JS debugger statement

@cjk provided this tip. The debugger like Firebug will pause at the debugger statement. It saves me the time to locate JS file, to go to the specific line and set breakpoint after re-deploying the application.

Snippets: https://github.com/redguardtoo/emacs.d/blob/master/snippets/js-mode/debugger.yasnippet

https://github.com/redguardtoo/emacs.d/blob/master/snippets/js-mode/debugger-cond-breakpoint-from-kill-ring.yasnippet

Bonus

Similar snippets for Emacs Lisp are defined HERE.

Use bootstrap+font-awesome+jquery for IE7 web application

bootstrap+font-awesome+jquery for IE7 web application :en:javascript:css:ie:

Live demo: http://binchen.org/schools/

Screenshot: e86f11a6-733c-11e4-8312-a6c8ad2b17be.png

Environment

  • Bootstrap 2.3.2
  • font-awesome 3.2.1
  • JQuery 1.8.2

I only use the grid layout from bootstrap and minimum API from jQuery.

Notes

  • IE7 does not support media query. So I abandon "mobile first" strategy, new strategy is "IE7 first".
  • IE7 does not support inline-block properly, I need below css code hack:
.myclass {
  display: inline-block;
  /* ie7*/
  *display: inline;
  zoom: 1;
}
  • bootstrap's row-fluid has issues in IE7. I use row-fluid in this case, but have to hard code the row width sometimes.
  • add below code into javascript so that I can hack IE7-only css code:
if($.browser.msie && parseFloat($.browser.version) < 8){
  that.ie7 = true;
  $('body').addClass('ie7');
}

Make web-mode support HTML with AngularJS data-binding

web-mode support HTML with AngularJS data-binding :en:emacs:angular:html:

I find it very useful to use `M-x imenu` to jump to the AngularJS binding point of one HTML file.

Insert below code into ~/.emacs:

(eval-after-load 'web-mode
  '(progn
     ;; angular imenu
     (add-to-list 'web-mode-imenu-regexp-list
                  '(" \\(ng-[a-z]*\\)=\"\\([a-zA-Z0-9]*\\)" 1 2 "="))))