Debug efficiently in Emacs

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.

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

Bonus

Similar snippets for Emacs Lisp are defined HERE.

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

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

Screenshot: https://cloud.githubusercontent.com/assets/184553/5156904/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');
}

Export org file embedded with code snippets

I use Org-mode to record all my notes.

As a developer, I place code snippets from many programming languages into one org file.

The issue is when exporting the org file, major mode for each language will be loaded to render the code snippet.

It means the hooks of the major modes will be executed. Since I put lots of heavy weight setup things in those hooks, my exporting is extremely slow.

In order to solve the issue, I write a small function which will be called at the beginning of each major mode hook.

The function basically check whether the `(buffer-file-name)` is the temporary file created by Org-mode or the output HTML converted from org file. If answer is "YES", then code in major mode hook will not be executed.

(defun is-buffer-file-temp ()
  (interactive)
  "If (buffer-file-name) is nil or a temp file or HTML file converted from org file"
  (let ((f (buffer-file-name))
        (rlt t))
    (if f
        (if (and (not (string-match temporary-file-directory f))
                 (not (file-exists-p (replace-regexp-in-string "\.html$" ".org" f))))
          (setq rlt nil)))
    rlt))

Another tip is that exporting shell code will fail if `sh-mode` is loaded. So I use "bash" instead. Since there is no "bash-mode", exporting will be fine.

How a programmer publish static HTML blog in Emacs

I can publish my blog in five seconds, by running only one Emacs command!

I will give you a minimum solution, then explain why and provide technical details.

Basic Emacs lisp programming knowledges are required.

Please note I'm NOT targeted at specific static site generator like Nikola or Jekyll.

Solution

Tested on Linux and OSX.

Requirements:

  • Org-mode bundled with Emacs 24.x
  • Nikola as the blog generator
  • FTP client ncftp
  • Latest org2nikola (v0.0.9+)
  • Insert below code into ~/.emacs
(defun org2nikola-after-hook-setup (title slug)
  (let ((url (concat "http://blog.yourdomain.net/posts/" slug ".html"))
        (nikola-dir (file-truename "~/projs/nikola-root"))
        (password "yourpassowrd")
        (username "yourusername")
        dir
        file
        lines
        rlt
        res
        cmd)
    (copy-yank-str title)
    (copy-yank-str url)
    (message "%s => kill-ring&clipboard" url)
    (setq rlt (shell-command-to-string (format "cd %s; nikola build" nikola-dir)))
    (setq lines (split-string rlt "\n"))
    (dolist (l lines)
      (when (string-match "output\\(.*/\\)*\\([^/]*\\)$" l)
        (setq dir (match-string 1 l))
        (setq file (match-string 2 l))
        (setq cmd (format "ncftpput -b -u %s -p %s ftp.yourdomain.net /blog%s %s/output%s%s"
                          username password dir nikola-dir dir file))
        (message "cmd=%s" cmd)
        (shell-command cmd)
        ))
    ))

(add-hook 'org2nikola-after-hook 'org2nikola-after-hook-setup)

You can write blog into org file, export and publicize it with command `M-x org2nikola-export-subtree`.

Why

I used Wordpress and Org2blog for a very long time. Then I turned to static blog generator because:

  • Wordpress is slow to load web page
  • I waste too much time to "manage" Wordpress (applying security patch, fighting with spam comment …)
  • As a programmer, I need publish code snippets. Wordpress is BAD at rendering code.

Yes, only wordpress sucks. For Org2blog, I can only say good things. Actually, this article is inspired purely by Puneeth Chaganti's excellent work on org2blog.

Technical Details

Generating HTML

As I said, Nikola is my blog generator which converts my org file into HTML.

But I prefer using Org-mode to do the HTML rendering. Nikola only need handle remaining minor book keeping stuff (creating RSS feed, for example).

It's because I want to minimize the dependency. I may switch to other blog generator in the future. But my web site always has the same look and feel because the HTML is generated by Org-mode.

I learned this trick from Org2blog.

If we only use org-mode to create HTML, then extracting code snippet is really easy. All we need is to use regular expression to extract the content between HTML tag "<pre>".

Actually I don't even bother doing the extraction. I just replace all the "<pre>" tag with my own tag because I assume the HTML produced by org-mode is stable.

Here is the Emacs lisp code I borrowed from Org2blog:

(defun org2nikola-replace-pre (html)
  "Replace pre blocks with sourcecode shortcode blocks.
shamelessly copied from org2blog/wp-replace-pre()"
  (save-excursion
    (let (pos code lang info params header code-start code-end html-attrs pre-class)
      (with-temp-buffer
        (insert html)
        (goto-char (point-min))
        (save-match-data
          (while (re-search-forward "<pre\\(.*?\\)>" nil t 1)

            ;; When the codeblock is a src_block
            (unless
                (save-match-data
                  (setq pre-class (match-string-no-properties 1))
                  (string-match "example" pre-class))
              ;; Replace the <pre...> text
              (setq lang (replace-regexp-in-string ".*src-\\([a-zA-Z0-9]+\\).*" "\\1" pre-class)  )

              (replace-match "")
              (setq code-start (point))

              ;; Go to end of code and remove </pre>
              (re-search-forward "</pre.*?>" nil t 1)
              (replace-match "")
              (setq code-end (point))
              (setq code (buffer-substring-no-properties code-start code-end))

              ;; Delete the code
              (delete-region code-start code-end)
              ;; Stripping out all the code highlighting done by htmlize
              (setq code (replace-regexp-in-string "<.*?>" "" code))
              ;; class linenums will add stripes which will destory the 3rd party skins
              (insert (concat "\n<pre class=\"prettyprint lang-"
                              (org2nikola-fix-unsupported-language lang)
                              "\">\n"
                              code
                              "</pre>\n"))
              )))

        ;; Get the new html!
        (setq html (buffer-substring-no-properties (point-min) (point-max))))
      ))
  html)

The code snippet inside "<pre>" tag could be rendered by 3rd party javascript library google-code-prettify or SyntaxHighlighter.

BTW, the last straw that push me away the Wordpress is its wrapper of SyntaxHighlighter. SyntaxHighlighter is a beautiful and user-friendly library. But the wrapper forces me to tweak the php code in terrible editor from Wordpress.

Create blog posts

Emacs creates HTML files and meta files. Things created by Emacs will be copied into Nikola's folder.

Nikola's duty is simple. Basically it only copy my stuff from its input folder to its output folder when I trigger "nikola build" command.

The "nikola build" command will also dump the list of files to be uploaded into stdout.

The build message dumped:

Scanning posts....done!
.  render_archive:output/2014/index.html
.  render_sources:output/posts/jump-to-the-positions-before-and-after-m-x-imenu.wp
.  render_posts:cache/posts/jump-to-the-positions-before-and-after-m-x-imenu.html
.  render_indexes:output/index.html
.  render_indexes:output/index-17.html
.  render_tags:output/categories/en.html
.  render_tags:output/categories/emacs.html
.  render_tags:output/assets/js/tag_cloud_data.json
.  render_tags:output/categories/emacs.xml
.  generate_rss:output/rss.xml
.  render_pages:output/posts/jump-to-the-positions-before-and-after-m-x-imenu.html
.  render_pages:output/posts/why-emacs-is-better-editor-part-two.html
.  render_tags:output/categories/en.xml

Only the files in sub-folder "output" need be uploaded.

Preprocess nikola output

It's all done by Emacs Lisp.

The final output contain lines like:

ncftpput -b -u yourname -p yourpassword ftp.yourdomain.net /blog/2014/ /home/yourname/nikola-root/output/2014/index.html

As you can see, each line is a ftp upload command we need execute in shell.

FTP upload

Ncftp is my choice of FTP client. It's solid and efficient.

Its command line tool "ncftpput" has a flag "-b". With the flag ncftpput will start a daemon at background and handles the ftp upload as a batch job submit. It means ftp connection will be reused and the user is not blocked by the upload operation. So the upload operation is extremely fast.

I use Emacs Lisp API `file-truename` to convert all relative path to absolute path to avoid any platform (OSX) issue.

BTW, you can `cat ~/.ncftp/spool/log` to check the FTP upload status.

Summary

The workflow is simple.

Emacs and third party JS libraries are responsible for the look and feel of my website.

Blog generator like Nikola is just a thin layer to relay the HTML files created by Emacs.

Ncftp will upload HTML files. It's the best FTP client which could be easily integrated into Emacs.

Jump to the positions before and after `M-x imenu`

As a programmer, I use `M-x imenu` to jump to the callee when editing a code file. After a little code tweaking in the callee, I need jump back to caller as quickly as possible.

1 Solution

Insert below code to ~/.emacs:

(defvar rimenu-position-pair nil "positions before and after imenu jump")
(add-hook 'imenu-after-jump-hook
          (lambda ()
            (let ((start-point (marker-position (car mark-ring)))
                  (end-point (point)))
              (setq rimenu-position-pair (list start-point end-point)))))

(defun rimenu-jump ()
  "jump to the closest before/after position of latest imenu jump"
  (interactive)
  (when rimenu-position-pair
    (let ((p1 (car rimenu-position-pair))
          (p2 (cadr rimenu-position-pair)))

      ;; jump to the far way point of the rimenu-position-pair
      (if (< (abs (- (point) p1))
             (abs (- (point) p2)))
          (goto-char p2)
          (goto-char p1))
      )))

Now you can use `M-x rimenu-jump` to jump.

2 Technical details

Imenu will push the start point into mark-ring. After reaching the destination, it will call the imenu-after-jump-hook where I can store the end point.

I store the start/end point into rimenu-position-pair and `M-x rimenu-jump` goes to the farthest one from the pair.

Here is the original discussion on G+. Thanks to Jorge A. Alfaro Murillo for enlightening me on the solution.

Why Emacs is a better editor, part two

If you are impatient, jump to the "Quick Start" and paste my setup into your ~/.emacs. That's all you need to do!

No extra setup needed! Then keep using your js2-mode happily, as if nothing happened. ;)

Problem

In my previous article Why Emacs is better editor - a case study for javascript developer, I proved that Emacs is better than Sublime Text.

So for this so-called "Goto Symbol" feature, Emacs wins.

It's because we use a plugin js2-mode. It's actually a javascript parser which creates the symbols from AST.

But in real world, regular expression is better, sometimes.

In modern MVC javascript frameworks (Angular, for example), you will meet below code,

app.controller('MyController', function ($scope, $http) {
  // ...
});

As you can see, using regular expression to extract the string "MyController" is more versatile and simpler.

Solution

My latest contribution to js2-mode solves this problem perfectly. It combines the powers of AST and regular expression.

The js2-mode will integrate this feature soon. I will notify you when next version is ready.

Let's cut off the boring technical details and see the demo,

For a simple hello.js with below content,

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

function test() {
  console.log('test called');
}

app.controller('MyController', function ($scope, $http) {
  console.log('MyController registered');

  var that = this;

  $scope.test1 = function() {
    console.log('$scope.test called');
  };

  $scope.hello = function() {
    console.log('$scope.hello called');
  };

  $scope.fn1 = function () {

    function test() {
      console.log('hello world');
    };
    console.log('hello');
  };
});

Emacs: https://cloud.githubusercontent.com/assets/184553/4871228/3ebe6588-61a5-11e4-9e9f-e6eb8218e2ee.png

Sublime3 (build 3047): https://cloud.githubusercontent.com/assets/184553/4871347/c33b8fee-61ae-11e4-8072-671935b68d6b.png

Please note Emacs displays two functions with the same name "test" correctly!

BTW, my previous "Why Emacs is better" article got many feedbacks from Sublime users.

One feedback is that my comparison is not fair because I'm comparing Emacs plugin with naked Sublime. Though I did some research before writing the article, I could be wrong. Please enlighten me if you know such Sublime plugins.

Another valuable feedback is that native Sublime provides better experience out of the box for junior developers. I admit that's a good point. But Emacs provides many awesome choices out of the box if junior guys start from setups of the masters (like Steven Purcell).

Sublime users also argue that Sublime3 uses Python. Python is a better programming language. I'm qualified to answer this question because I wrote some large commercial Python application when I worked in Kodak R&D. First version I used was v2.2. So I've got about 10 years experience in Python. And I write lots of Emacs Lisp code these days. My opinion is that both languages are good enough as DSL for text editors. In Python, you can use OO. In Emacs Lisp, you can treat function as object and there are Macros and Advising. Both languages have enough widgets to shoot yourself in the foot. Python is surely newbie-friendly. But number of newbies doesn't matter in high-end rival.

Quick Start

I'm still discussing with the js2-mode maintainer Dmitry Gutov about the best way to merge my patch.

Dmitry Gutov updated the algorithm to parse the imenu items by walking the AST instead. It's better than my REGEX hacking because AST could show the context of the function.

But my patch is still useful for extract strings from modern JS framework, as I've shown you in Angular example. I'm just updating my pull request to be compatible with the new AST walk algorithm.

In the meantime, you can paste below code into your ~/.emacs before the patch is officially merged.

;; below regex list could be used in both js-mode and js2-mode
(setq javascript-common-imenu-regex-list
      '(("Controller" "\.controller( *'\\([^']+\\)" 1)
        ("Filter" "\.filter( *'\\([^']+\\)" 1)
        ("Factory" "\.factory( *'\\([^']+\\)" 1)
        ("Service" "\.service( *'\\([^']+\\)" 1)
        ("Directive" "\.directive( *'\\([^']+\\)" 1)
        ("Event" "\.\$on( *'\\([^']+\\)" 1)
        ("Config" "\.config( *function *( *\\([^\)]+\\)" 1)
        ("Config" "\.config( *\\[ *'\\([^']+\\)" 1)
        ("OnChange" " *\$('\\([^']*\\)').*\.change *( *function" 1)
        ("OnClick" " *\$('\\([^']*\\)').*\.click *( *function" 1)
        ("Watch" "\.\$watch( *'\\([^']+\\)" 1)
        ("Function" "function\\s-+\\([^ ]+\\)(" 1)
        ("Function" " \\([^ ]+\\)\\s-*=\\s-*function\\s-*(" 1)))

;; {{ patching imenu in js2-mode
(setq js2-imenu-extra-generic-expression javascript-common-imenu-regex-list)

(defvar js2-imenu-original-item-lines nil
  "List of line infomration of original imenu items.")

(defun js2-imenu--get-line-start-end (pos)
  (let (b e)
    (save-excursion
      (goto-char pos)
      (setq b (line-beginning-position))
      (setq e (line-end-position)))
    (list b e)))

(defun js2-imenu--get-pos (item)
  (let (val)
    (cond
     ((integerp item)
      (setq val item))

     ((markerp item)
      (setq val (marker-position item))))

    val))

(defun js2-imenu--get-extra-item-pos (item)
  (let (val)
    (cond
     ((integerp item)
      (setq val item))

     ((markerp item)
      (setq val (marker-position item)))

     ;; plist
     ((and (listp item) (listp (cdr item)))
      (setq val (js2-imenu--get-extra-item-pos (cadr item))))

     ;; alist
     ((and (listp item) (not (listp (cdr item))))
      (setq val (js2-imenu--get-extra-item-pos (cdr item)))))

    val))

(defun js2-imenu--extract-line-info (item)
  "Recursively parse the original imenu items created by js2-mode.
The line numbers of items will be extracted."
  (let (val)
    (if item
      (cond
       ;; Marker or line number
       ((setq val (js2-imenu--get-pos item))
        (push (js2-imenu--get-line-start-end val)
              js2-imenu-original-item-lines))

       ;; The item is Alist, example: (hello . 163)
       ((and (listp item) (not (listp (cdr item))))
        (setq val (js2-imenu--get-pos (cdr item)))
        (if val (push (js2-imenu--get-line-start-end val)
                      js2-imenu-original-item-lines)))

       ;; The item is a Plist
       ((and (listp item) (listp (cdr item)))
        (js2-imenu--extract-line-info (cadr item))
        (js2-imenu--extract-line-info (cdr item)))

       ;;Error handling
       (t (message "Impossible to here! item=%s" item)
          )))
    ))

(defun js2-imenu--item-exist (pos lines)
  "Try to detect does POS belong to some LINE"
  (let (rlt)
    (dolist (line lines)
      (if (and (< pos (cadr line)) (>= pos (car line)))
          (setq rlt t)))
    rlt))

(defun js2-imenu--is-item-already-created (item)
  (unless (js2-imenu--item-exist
           (js2-imenu--get-extra-item-pos item)
           js2-imenu-original-item-lines)
    item))

(defun js2-imenu--check-single-item (r)
  (cond
   ((and (listp (cdr r)))
    (let (new-types)
      (setq new-types
            (delq nil (mapcar 'js2-imenu--is-item-already-created (cdr r))))
      (if new-types (setcdr r (delq nil new-types))
        (setq r nil))))
   (t (if (js2-imenu--item-exist (js2-imenu--get-extra-item-pos r)
                                 js2-imenu-original-item-lines)
          (setq r nil))))
  r)

(defun js2-imenu--remove-duplicate-items (extra-rlt)
  (delq nil (mapcar 'js2-imenu--check-single-item extra-rlt)))

(defun js2-imenu--merge-imenu-items (rlt extra-rlt)
  "RLT contains imenu items created from AST.
EXTRA-RLT contains items parsed with simple regex.
Merge RLT and EXTRA-RLT, items in RLT has *higher* priority."
  ;; Clear the lines.
  (set (make-variable-buffer-local 'js2-imenu-original-item-lines) nil)
  ;; Analyze the original imenu items created from AST,
  ;; I only care about line number.
  (dolist (item rlt)
    (js2-imenu--extract-line-info item))

  ;; @see https://gist.github.com/redguardtoo/558ea0133daa72010b73#file-hello-js
  ;; EXTRA-RLT sample:
  ;; ((function ("hello" . #<marker 63>) ("bye" . #<marker 128>))
  ;;  (controller ("MyController" . #<marker 128))
  ;;  (hellworld . #<marker 161>))
  (setq extra-rlt (js2-imenu--remove-duplicate-items extra-rlt))
  (append rlt extra-rlt))

(eval-after-load 'js2-mode
  '(progn
     (defadvice js2-mode-create-imenu-index (around my-js2-mode-create-imenu-index activate)
       (let (extra-rlt)
         ad-do-it
         (setq extra-rlt
               (save-excursion
                 (imenu--generic-function js2-imenu-extra-generic-expression)))
         (setq ad-return-value (js2-imenu--merge-imenu-items ad-return-value extra-rlt))
         ad-return-value))
     ))
;; }}

My Emacs skill is improved after 3 years

This is my note on useful commands/keybindings to memorize three years ago.

Most are obsolete because I'm more skillful now.

Basically I use fewer but more powerful plugins. I write Emacs lisp if there is no suitable plugin.

  • Three years ago, column edit,
C-x r t yourstring RET (See "How to do select column then do editing in GNU Emacs ?"")

Now I use Evil

  • Three years ago, save current position to register and jump to the position,
C-r SPC to save, C-j to jump (better-registers.el required) 

Now Evil.

  • Three years ago, save frame configuration to register,
C-r f (better-registers.el required) 

Now workgroups2.

  • Three year ago, (un)comment line,
M-; (qiang-comment-dwim-line required)

Now evil-nerd-commenter.

  • Three years ago for visiting the next/previous error message after compiling,
"M-g M-n" or "M-g M-p"

I'm still using it.

  • Three years ago, find-tag/pop-tag-mark
"M-." and "M-*"

Now Evil

  • Three years ago, grep current work directory only or all sub-directories
M-x lgrep/rgrep

Now grep in shell plus percol

  • Three years ago, visit multiple tags table
M-x visit-tags-table

hack tags-table-list directly might be simpler.

  • Three years ago, set countdown timer
M-x org-timer-set-timer, C-c C-x ;

Now I don't push myself with the timer thing.

  • Three years ago, mark subtree in org-mode
M-x org-mark-subtree

It was used to select the text to post to my blog.

Now I use org2nikola. The org-mark-subtree is hardcoded into org2nikola.

How to accept the github pull request efficiently

I use keyboard only in order to maximize my productivity.

Preparation

Install Firefox addon Keysnail and its plugin HOK. From now on, all the web browsing is done ONLY in keyboard.

Step 1

Open pull request page at github.com, click the link "command line".

In a real world project, I rarely accept a pull request without some modification. So I usually avoid pressing the big green button "merge pull request" on that page.

In the "command line" page, github is kind enough to list the command lines to "check out a new branch and test the changes" from the pull request. The command lines are like:

git checkout -b her-master master
git pull git@github.com:her/myproject.git master

I have installed a Greasemonkey user script NinjaWebCoder in order to use keyboard to copy those command lines from the browser into clipboard. Then I paste the command lines into terminal.

Step 2

I open the code file with Emacs.

There is an Emacs addon called git-gutter. I use its command "git-gutter:next-hunk" to move the cursor to the next "diff hunk".

Let me explain what's the diff hunk. When you edit some code under git's control, you code has some difference with the HEAD version. Every difference corresponds to the pair of a file name and a line number. That file-name-line-number pair is defined as a "diff hunk".

Now comes the most important part of this article. Since version 0.71, git-gutter added a new command "git-gutter:set-start-version". If I "git-gutter:set-start-revision" to the "HEAD^" version. I can jump to "diff hunk" of the difference between "HEAD" and "HEAD^".

In short, I can review the latest commit and change the code in one step.

Done

After review and code change, the remaining book-keeping things (git-commit/git-merge/git-push) are easy.

Every operation in previous steps is optimized with some shortcut. For example, in shell I use alias "g" instead of full command line "git status".

Please enlighten me if the work flow could be improved.

How to do the file navigation efficiently

My way is possibily not the best in the world. But I'm sure it's close to world class level because I didn't figure out the solution by myself. People who are much better than me provided the design. I'm just the guy who did the implementation.

Mendel Cooper provided the original idea in his famous Advanced Bash-Scripting Guide. Masafumi Oyamada (AKA mooz) added the missing piece by creating percol.

Problem

You need find full path of a file in a huge project.

The information you've got about the file is in-complete:

  • could be a relative path like "../part/of/path/file-name.txt"
  • could be a file name without file extension like "helloword"

When you got the full path of that file, you want to share it to other applications easily.

The whole work flow should be intuitive and efficient.

Installation

Step 1, Install percol Download the package and extract it. Place the sub-directory named "percol/" into the directory "~/bin". Rename the program "percol" in sub-directory "bin" into "percol.py". Put the percol.py also into "~/bin".

Step 2, Insert below code into ~/.bashrc:

[ $(uname -s | grep -c CYGWIN) -eq 1 ] && OS_NAME="CYGWIN" || OS_NAME=`uname -s`
function pclip() {
    if [ $OS_NAME == CYGWIN ]; then
        putclip $@;
    elif [ $OS_NAME == Darwin ]; then
        pbcopy $@;
    else
        if [ -x /usr/bin/xsel ]; then
            xsel -ib $@;
        else
            if [ -x /usr/bin/xclip ]; then
                xclip -selection c $@;
            else
                echo "Neither xsel or xclip is installed!"
            fi
        fi
    fi
}

function ff()
{
    local fullpath=$*
    local filename=${fullpath##*/} # remove "/" from the beginning
    filename=${filename##*./} # remove  ".../" from the beginning
    echo file=$filename
    #  only the filename without path is needed
    # filename should be reasonable
    local cli=`find $PWD -not -iwholename '*/target/*' -not -iwholename '*.svn*' -not -iwholename '*.git*' -not -iwholename '*.sass-cache*' -not -iwholename '*.hg*' -type f -iwholename '*'${filename}'*' -print | ~/bin/percol.py`
    echo ${cli}
    echo -n ${cli} |pclip;
}

Usage

Type "ff partials-of-file-path" in the bash shell. A filter window will popup. You can filter and scroll down/up in that window to select one file. The full path of the file will be copied into system clipboard automatically (under Linux, you need install either xsel or xclip to access clipboard).

The paritls-of-file-path could contain wildcard character. For example, "…/grunt-docs/*bootstrap*css" is a fine example.

Here is the screen shot when I type "ff el" in my ~/.emacs.d: use-ff-in-shell.png

You will notice that I input the string "inflect" to filter the result.

I can scroll up/down (press Ctrl-P or Ctrl-N) to select the exact file.

BTW, in Emacs community, many people tried to embed a file explorer into Emacs (Sr speedbar, for example). This solution may make embedded file explorer unnecessary. A Sublime guy once asked me to share this trick to him. I guess it's because Sublime's own file explorer is not good enough for him.

Advanced usage

What I have show you is only a prototype. I use it to demostrate the key idea about combining the power of bash/percol/clipboard.

What I actually use in real world is more advanced.

For example, here is the screen shot when I search the bash history. use-h-in-shell.png

Code to insert ~/.bashrc:

function h () {
    # reverse history, pick up one line, remove new line characters and put it into clipboard
    if [ -z "$1" ]; then
        history | sed '1!G;h;$!d' | ~/bin/percol.py | sed -n 's/^ *[0-9][0-9]* *\(.*\)$/\1/p'| tr -d '\n' | pclip
    else
        history | grep "$1" | sed '1!G;h;$!d' | ~/bin/percol.py | sed -n 's/^ *[0-9][0-9]* *\(.*\)$/\1/p'| tr -d '\n' | pclip
    fi
}

Another screen shot I select a file in my git commit. use-glsf-in-shell.png

Code:

function glsf () {
    local str=`git --no-pager log --oneline --stat $* |  ~/bin/percol.py`
    if [[ $str =~ ^[[:space:]]*([a-z0-9A-Z_.\/-]*).*$ ]]; then
        echo -n ${BASH_REMATCH[1]} |pclip;
        echo ${BASH_REMATCH[1]}
    fi
}

Install multiple versions of Emacs into $HOME directory from source

Here is the script,

#!/bin/bash
[ -z "$EMACS_VERSION" ] && EMACS_VERSION="23.4"
[ -z "$EMACS_URL" ] && EMACS_URL="http://gnu.mirror.uber.com.au/emacs"
# I've assign 12G memory to /tmp as ramdisk
[ -z "$EMACS_TMP" ] && EMACS_TMP="/tmp"

curl $EMACS_URL/emacs-$EMACS_VERSION.tar.gz | tar xvz -C $EMACS_TMP
cd $EMACS_TMP/emacs-$EMACS_VERSION;mkdir -p $HOME/myemacs/$EMACS_VERSION;rm -rf $HOME/myemacs/$EMACS_VERSION/*;./configure --prefix=$HOME/myemacs/$EMACS_VERSION --without-xpm --without-png --without-gif --without-tiff --without-jpeg --without-rsvg --without-xft --without-xaw3d --without-xim --without-xpm --without-dbus --without-gconf --without-makeinfo --with-x-toolkit=no --without-sound --without-sync-input --without-pop;make;make install

# clean the installation directory
 rm -rf $EMACS_TMP/emacs-$EMACS_VERSION

Usage:

./install-emacs
#or
EMACS_VERSION=23.4 ./install-emacs

The emacs will be installed into the directory $HOME/myemacs/$EMACS_VERSION

Contents © 2014 Chen Bin - Powered by Nikola