M-g vs. goto-line

Just as many of you, I always missed the goto-line command in Emacs having no key assigned by default despite its usefulness, and one day (when I moved from XEmacs) I bound it to M-g. However, starting with Emacs 22, they decided to have it bound to M-g M-g, along with previous-error which was bound to M-g M-p and next-error to M-g M-n. The last part looked nice especially when I wasn’t happy with C-x ` at all, but it was just far from acceptable to me that I had to type M-g twice for jumping to a specific line number, so I insisted on my binding of M-g.

Time has passed, and now I have a bit of skills in elisp, and here it goes:

(defun goto-line-number ()
  (interactive)
  (goto-line (string-to-number
              (read-from-minibuffer
               "Goto line: "
               (char-to-string last-command-event)))))
(loop for n from 1 to 9 do
      (global-set-key (format "\M-g%d" n) 'goto-line-number))
(global-set-key "\M-g?" 'describe-prefix-bindings)

This snippet makes it as if M-g were a key for goto-line by tweaking the key bindings of M-g 1..9. For example, typing M-g 2 lets you enter the minibuffer with the first figure (2) input, and you are ready to type the rest of the figures of a line number, just as below:

-UUU:@----F13  init.el        Top (1,0)     [1]  (Emacs-Lisp FlyC:1/5 hs AC yas)
Goto line: 2 

This means you can just say M-g 2 0 0 RET to get to the line 200 while you can say M-g M-n to jump to the next error line. Cool.

For those who may wonder, the last line of the snippet helps you remember other key combinations of the M-g family by typing ? after M-g. Happy hacking!

Toggling “hairy” features in cperl-mode of Emacs

Emacs’s cperl-mode has many “electric” features, but how would you turn them all at once? Here’s my implementation of cperl-toggle-hairy.

(eval-after-load "cperl-mode"
  '(progn
     (defun cperl-toggle-hairy ()
       (interactive)
       (unless (boundp 'cperl-electric-prev-alist)
         (set (make-local-variable 'cperl-electric-prev-alist) nil))
       (cond (cperl-electric-prev-alist
              (dolist (pair cperl-electric-prev-alist)
                (set (car pair) (cdr pair)))
              (abbrev-mode (if abbrev-mode 1 0))
              (setq cperl-electric-prev-alist nil)
              (message "cperl-toggle-hairy: on.")
              t)
             (t
              (dolist (var '(abbrev-mode
                             cperl-auto-newline
                             cperl-electric-keywords
                             cperl-electric-lbrace-space
                             cperl-electric-linefeed
                             cperl-electric-parens
                             cperl-hairy
                             cperl-indent-region-fix-constructs
                             cperl-info-on-command-no-prompt
                             cperl-lazy-help-time))
                (setq cperl-electric-prev-alist
                      (cons (cons var (symbol-value var)) cperl-electric-prev-alist))
                (set var nil))
              (message "cperl-toggle-hairy: off.")
              nil)))
 
     (define-key cperl-mode-map "\C-c\C-l" 'cperl-toggle-hairy)
 
     (defun cperl-electric-quote (arg)
       (interactive "P")
       (self-insert-command (prefix-numeric-value arg))
       (and
        (cperl-val 'cperl-electric-parens)
        (let ((properties (text-properties-at (point))))
          (and (null (memq 'font-lock-string-face properties))
               (null (memq 'font-lock-comment-face properties))
               (save-excursion (insert last-command-event))))))
     (define-key cperl-mode-map "\"" 'cperl-electric-quote)
     (define-key cperl-mode-map "'"  'cperl-electric-quote)))

As you see, I put cperl-electric-quote as a New Year’s present.

Pasting a text literally to Emacs under terminal

A text editor is distinguished from "cat > file" where it greatly assists the user in writing a structured text by offering such features as auto-indentation, auto-correction, completion for programming, etc., not to mention basic editing features like search, mass replace, cut, copy and paste. These advanced features are quite nice especially in programming, but sometimes they bite.
Suppose you are under terminal editing a ruby program in Emacs with ruby-electric-mode turned on, feeling like pasting a text from outside the editor like clipboard, GNU screen or tmux. You could easily imagine what would happen. The pasted text would be messed up with superfluous closing parenthesis and quotations that are automatically inserted during the character-by-character pasting process. Of course ruby-electric-mode is a minor mode that can be easily turned off, but for example, in cperl-mode there are many individual flags for a variety of its “electric” features and they cannot be turned off all at once (see the following post for cperl-toggle-hairy). Anyway, changing the editing state back and forth is definitely not something you want just for a copy & paste. It is not supposed to be such a pain in the first place.

Here’s my quick answer.

(defun ins ()
  (interactive)
  (let* ((target (current-buffer))
         (buffer (generate-new-buffer (format "*Paste Buffer for <%s>*"
                                              (buffer-name target)))))
    (with-current-buffer buffer
      (make-local-variable 'ins-target-buffer)
      (setq ins-target-buffer target)
      (local-set-key "\C-c\C-c"
                     '(lambda ()
                        (interactive)
                        (let ((buffer (current-buffer)))
                          (switch-to-buffer ins-target-buffer)
                          (insert-buffer-substring buffer)
                          (kill-buffer buffer))))
      (local-set-key "\C-c\C-k"
                     '(lambda ()
                        (interactive)
                        (let ((buffer (current-buffer)))
                          (switch-to-buffer ins-target-buffer)
                          (kill-buffer buffer)))))
    (switch-to-buffer buffer)
    (message (format "Enter a text to paste.  Type C-c C-c when done. (C-c C-k to dismiss)"
                     (buffer-name target)))))

Whenever you feel like pasting a text from clipboard at point, type M-x ins, paste, type C-c C-c and it’s done. Simple.

git-cp: cp(1) + git-add(1)

I just wrote a quick but functionally decent implementation of git-cp(1), a missing subcommand that Subversion users would certainly get used to.  While I desperately wanted it I could imagine why it is not part of the Git core.  There are many forms of changeset that could make it difficult for SCM to trace history, and keeping track of file copies would only handle a small part of the problem.  For example, you could merge two files into one, move a portion from one file to another, or split a file into two, and recording a hint that a file copy is done would only serve its purpose in the last case.  The designers of Git had the insight to recognize that and instead of adding a feature to record a copy, they equipped git-log and git-diff with copy/move detection so that a copy can be found when they look back the history, making recorded hints unnecessary as the detection method gets mature enough to work effectively.
But, but. I had been tired of typing cp a b && git add b every time I feel like using an existing file as a template for making a new file, and thought it would feel great if you could just say git cp a b to start working on the new file quickly. Some may say the name git cp is misleading but it is just as evil as git mv that is too just a type saver. Plus, git-cp is made not to overwrite an existing file or copy a file to outside of the repository, so it is safe to use.
I also wrote git-touch(1) for a similar reason. Whenever I want to create a new empty directory, I can say git touch log/.gitignore instead of a lengthy sequence of mkdir log && touch log/.gitignore && git add log/.gitignore. There’s also git-untouch(1) to undo it when you have made a typo or changed your mind on the new file path.
These are all dedicated to those who joy in typing less.

bcbiff(1) – Boxcar based IMAP biff

There’s one big common frustration among Gmail users on iPhone that you cannot receive instant notification for coming mails.  On the other hand, you know that Boxcar is one of the must-haves for iPhone bringing you a fully customizable push notification method that is flexible and easy to use.

It may seem enabling push notification for Gmail is as easy as setting up your Gmail account to forward important mails to your Boxcar address, but it actually isn’t.  Suppose you want to be only notified of mails hitting Inbox because you receive tons of mails on mailing lists or mail magazines that are tagged and archived by filters. You’ll find it not easy to write a filter rule that matches mails that will be dropping in Inbox, because in Gmail all filters are evaluated in parallel and you cannot define a rule based on tags added by other filters.

Another concern is your privacy.  Even if you managed to make up a filter rule and successfully set up forwarding to Boxcar, every mail forwarded would be relayed through external servers and end up having a copy on Boxcar.  I’m not saying I don’t trust Boxcar or relay servers, but there’s no point for you (nor for them!) to carry a private mail content including sensitive headers outside of Gmail just for notifying that a mail has received.

Here’s why I wrote bcbiff(1).  Bcbiff is a tool that checks the Inbox folder (or any specified folders) on an IMAP server for unread mails and sends a notification via Boxcar for each.  Mail bodies and headers other than From, Date and Subject are stripped off, and the Message-Id’s of the latest 100 unread mails are cached so that you get just one notification per mail.  Bcbiff supports multiple mailboxes and Boxcar accounts.

To use it, you need a server on which you can periodically run bcbiff (Ruby 1.8.7+ or 1.9.2+ required) and send email via the sendmail(1) command.  See README for details.

Have fun with it!

git-shift: Changing commit timestamps in Git

Git is known to be so flexible that one can even fix or delete old commits, but what would you do if you wanted to change the timestamp of a particular commit? This need can arise for various reasons, such as when a merged commit has picked the unwanted timestamp on squash, when a contributor’s (or your notebook’s) machine clock was obviously far out of sync, and so on.
git-shift (update: repository moved) is the tool I wrote to change dates (timestamps) of specified commits in a git repository.  The choice of the name sounds a bit too bold, so maybe I will rename it later—but anyway.
The usage is simple; specify the amount of time you want to shift timestamps by, and a list of commit IDs you want to change timestamps of. (Range notation is currently not supported)
e.g. to move timestamps of commits identified by the IDs f9e8d7c6 and 579acf two hours backwards:

$ git-shift -2h f9e8d7c6 579acf

Use it wisely and enjoy your life, night and day workers! :D

git-info: Displaying information about a Git repository a la `svn info’

I haven’t been writing much in English about technical stuff, so I am going to start this blog with a series of articles that introduce some of my little works.

I picked git-info(1) to begin with, which is a small shell script that provides Git with a similar functionality to “svn info“.  As a long time user of Subversion who was new to Git I really missed a handy command to see repository information at a glance, so I wrote this.

knu’s git-info at master – GitHub

A sample output (at the time of writing) is as follows.

% git info
Repository Path: /home/knu/src/github/git-info/.git
Path: /home/knu/src/github/git-info
Remote Repositories:
    origin  git@github.com:knu/git-info.git (fetch)
    origin  git@github.com:knu/git-info.git (push)
Remote Branches:
      origin/HEAD -> origin/master
      origin/master
Local Branches:
    * master
Repository Configuration:
    [core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
    [remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = git@github.com:knu/git-info.git
    [branch "master"]
        remote = origin
        merge = refs/heads/master
Last Changed Commit ID: da32fa59f7fab84606ce3c144e636043e96d8063
Last Changed Author: Akinori MUSHA
Last Changed Date: Tue Jul 28 10:37:09 2009 +0900
Last Changed Log:
    Take the directory as a physical path.

Hope this helps you on your way.