読者です 読者をやめる 読者になる 読者になる

Slip Ahead Logging

It's not your fault at all.

wget に a.zip?foo=bar を a.zip として保存させるオプション

http://superuser.com/questions/61025/how-can-i-make-wget-rename-downloaded-files-to-not-include-the-query-string より。

wget でクエリパラメータつき URL をダウンロードするときには

wget "a.zip?foo=bar"

などとするが、こうすると a.zip?foo=bar というファイルができてしまう。

このときクエリパラメータを取り除いた a.zip というファイルをつくりたければ --content-disposition オプションを加えて

wget --content-disposition "a.zip?foo=bar"

とする。便利だ。

percol で z / autojump のようにディレクトリ高速ジャンプ

zsh percol

zsh の z / autojmp が話題になっているようです.これら二つのプラグインは実のところ試したことがないのですが「一度でも cd したことのあるディレクトリに効率よく cd する」という目的で作られたツールであると,理解しています.

さて,私も先の二つではありませんが,拙作の percol を使って「一度でも cd したことのあるディレクトリに効率よく cd する」ということをしていたので,ここぞとばかりに設定を共有しておこうと思います.

percol

percol はコマンドラインで手軽に anything.el / helm.el 的な汎用インタフェースを使いたい,という要求により開発をはじめたツールです.id:kbkbkbkb1 さんによる次の紹介文がわかりやすいでしょう.

percol は入力の1行を1候補として,部分一致かつ AND 検索で絞り込みし,選択した候補を出力するコマンドです.端的に言えば Emacsanything.el のコマンド版です.

http://d.hatena.ne.jp/kbkbkbkb1/20120429/1335835500

さまざまな方々が紹介記事を書いてくださっているので,そちらを読んで頂くのが良いでしょう.

設定

さて,今回のテーマである「percol で一度でも cd したことのあるディレクトリに効率よく cd する」設定を以下に掲載します.

# {{{
# cd 履歴を記録
typeset -U chpwd_functions
CD_HISTORY_FILE=${HOME}/.cd_history_file # cd 履歴の記録先ファイル
function chpwd_record_history() {
    echo $PWD >> ${CD_HISTORY_FILE}
}
chpwd_functions=($chpwd_functions chpwd_record_history)

# percol を使って cd 履歴の中からディレクトリを選択
# 過去の訪問回数が多いほど選択候補の上に来る
function percol_get_destination_from_history() {
    sort ${CD_HISTORY_FILE} | uniq -c | sort -r | \
        sed -e 's/^[ ]*[0-9]*[ ]*//' | \
        sed -e s"/^${HOME//\//\\/}/~/" | \
        percol | xargs echo
}

# percol を使って cd 履歴の中からディレクトリを選択し cd するウィジェット
function percol_cd_history() {
    local destination=$(percol_get_destination_from_history)
    [ -n $destination ] && cd ${destination/#\~/${HOME}}
    zle reset-prompt
}
zle -N percol_cd_history

# percol を使って cd 履歴の中からディレクトリを選択し,現在のカーソル位置に挿入するウィジェット
function percol_insert_history() {
    local destination=$(percol_get_destination_from_history)
    if [ $? -eq 0 ]; then
        local new_left="${LBUFFER} ${destination} "
        BUFFER=${new_left}${RBUFFER}
        CURSOR=${#new_left}
    fi
    zle reset-prompt
}
zle -N percol_insert_history
# }}}

# C-x ; でディレクトリに cd
# C-x i でディレクトリを挿入
bindkey '^x;' percol_cd_history
bindkey '^xi' percol_insert_history

# auto-fu.zsh を使っているのなら以下も
bindkey -M afu '^x;' percol_cd_history
bindkey -M afu '^xi' percol_insert_history

この設定により,

  • percol_cd_history
  • percol_insert_history

という二つのコマンド (zle ウィジェット, つまりキーバインド割り当てが可能) が追加されます.上の設定では,一番下の部分でそれぞれのコマンドを C-x ; と C-x i に割り当てています.必要に応じて変更してください.

この設定の特長は以下のようなところでしょうか.

  • percol のマッチング機能が使える
  • cd 回数も記録するので,よく訪問するディレクトリにジャンプしやすい
  • ジャンプだけでなく挿入もできる
    • コマンドの引数入力時などに役立つ

${HOME}/.cd_history_file に独自に cd 履歴を記録するので,設定を .zshrc に張り付けた当初はほとんど役に立ちません.cd を繰り返すことで便利になってきます.よく cd するディレクトリをあらかじめこのファイルに追加してしまう,というのも一つの手でしょう.1行1パスの単純なファイルなので,インポートも容易かと思います.

個人的には満足してしまっているのですが,z / autojump はさらに便利であったりするのかもしれません.機会を見てこれらも試してみようと考えています.

tmux を zsh からワンキーで起動したい

zsh

tmux を zsh からワンキーで起動したいなと思い,単純に tmux を起動する zle widget を定義した.

function tmux-attach() {
    { tmux list-sessions >& /dev/null && tmux attach } || tmux
}
zle -N tmux-attach
bindkey '^T' tmux-attach

しかし,これを zsh 上で Ctrl+t を押して起動しようとすると,

tmux: not a terminal

というエラーが発生して動かない.

zle の widget 中からエディタを起動する際のテクニックにexec < /dev/tty として widget の中で起動されるプログラムに tty を割り当てる(ユーザのキー入力がきちんとエディタに渡るようにしてあげる)方法があることを思い出し試してみるも,tmux に対しては有効ではなかった.

exec < /dev/tty Reassigns standard input back to the keyboard

http://www.ahinc.com/aix/kornsh.htm

あまり時間もかけていられないので,ひとまずは zsh のコマンドラインに直接 tmux コマンドを流し込んだあとでコマンドを実行するという,いかにもキーボードマクロ的でダサい方式でしのぐことに.

コマンドの実行履歴に { tmux list-sessions >& /dev/null && tmux attach } || tmux が残ってしまうという問題があるので*1,もっとスマートな方法が知りたいところ.

追記

考えてみれば zsh デフォルトの run-help (Alt+h に割り当てられている) も同様のことをやっていて,こちらも履歴には「run-help 該当コマンド名」が残る.これで問題ないということだろうか.

*1:hist_ignore_space オプションを設定している方であればこの問題は発生しない

git blame でインデントや改行コードの変更を無視

git emacs

git blame に -w オプションを渡すと「インデントや改行コードの変更に関わるコミット」を無視して blame をおこなってくれる.例えば改行コードを変更するようなコミットがあると通常の git blame では全ての行がそのコミットに汚染されてしまうのだが,この -w オプションを渡すことで,そのコミットを無視して実のある変更点だけを見ることができる.

  • w

Ignore whitespace when comparing the parents version and
the childs to find where the lines came from.

magit.el の magit-blame にはこのオプションを指定する機能がないので advice を使って一部の関数の挙動を変更することで対応した.

これで,インデントや改行コードの変更もためらわずにおこなえる.

JS > bytecode

javascript ecmascript

「いまどきの JavaScript 処理系ってほとんど VM 型なんでしょ.で,中では JavaScript のコードが VM 命令列に変換されて,実行されてるんでしょ.ならその VM 命令を標準化しちゃえば良いのに.そうすれば CoffeeScript や HaXe や TypeScript みたいな Transpiler 型の言語も VM 命令を直接吐けて効率的になりそうだし」

そんな誰もが思ったことのありそうな(いくぶん聞き飽きた)疑問に対する Brendan Eich 御大のお答え*1

"JavaScript > bytecode"

  • Dynamic typing ⇒ no verification
  • Type inference ⇒ delayed optimization
  • Would bytecode compress as well?
  • Bytecode standardization would suck
  • Bytecode versioning would suck more
  • Low-level bytecode is future-hostile
  • Many humans like writing JavaScript
http://brendaneich.github.com/Strange-Loop-2012/#/27

バイトコードの Verification

(専門家ではないので,変なことを言っている可能性があります)

「これを実行してね」と与えられたバイト列をそのまま命令列として VM に流し込んで実行するわけにいかないのは当然.これは JavaScript のコードに構文エラーがあったら実行できないのと同じ.さらに,バイト列が一見きちんとした命令列になっていても,内部でのジャンプ命令がおかしなアドレスに飛んでいたりだとか,不正なメモリアクセスをしていたりだとかいう危険なことをおこなっている可能性があるので,結局は実行前にそれらをチェックしなければいけない.これがバイトコードの Verification と呼ばれているもの.言語機能と VM 命令のレベルによっては,変数の初期化がおこなわれているかだとか,型チェックだとか,private / protected をはじめとするアクセス範囲が守られているかだとか,そういったことまでしっかりチェックしてあげる必要がある.

いっぽう JavaScript 処理系はバイトコードを実行しているわけではなく JavaScript コードをパースして独自の VM 命令列に変換し,それを順に実行する.処理系が責任を持って内部的にバイト列をメモリ上に生成するので,おかしなものが混じっている心配もない.

JavaScript にはパース処理が必要なように,バイトコードには Verification が必要.で,最近の JavaScript 処理系のパース処理は頑張っているものだから,きちんと Verification をやるよりもよっぽど高速だったりするらしい.

*1:この人達があげつらうバイトコードというのが大抵は JVM のものであるところが,毎度ながら気になるところではある

ブラウザ内のエディタ事情

event javascript mozilla

Mozilla 勉強会@東京 7th | Mozilla Developer Street (modest) で『ブラウザ内のエディタ事情』という発表をさせて頂きました.発表資料を以下に掲載します.

発表では,次の 3 トピックについて説明をおこないました.興味のある方は資料をご覧頂ければと思います.

  1. textarea 内で高度な編集機能を実現するための基本機能
  2. 様々なソースコードエディタ実装を分類して実装方針
  3. Firefox 11 から利用可能となった source-code.jsm

所感

やはり発表をさせて頂くことになると調査せざるを得ないもので,かなり勉強をさせて頂くことができました.特に面白かったのが CodeMirror の実装.内部を詳しく覗いたわけではありませんが 現在の設計に至るまでの事情を説明した記事 を作者が著しており,これが技術者心をくすぐる内容となっています.

余談ですが CodeMirror の作者である Marijn Haverbeke さんは Common Lisper であり,また JavaScript の良著と聞く Eloquent JavaScript の著者でもあるんですね.先ほどの技術文書のマニアックも,さもありなんというところです.

単語の途中に改行が入っていても正しく query-replace する

emacs

(M-q などで) fill-paragraph として一行の文字数を制限することはよくある.このとき,日本語の文章では単語の途中へ改行が入ってしまうことがあり,これが様々な問題を引き起こしていた.例えば,あとから表記揺れをなくすために query-replace で単語の置換をやった場合に,途中に改行が入ってしまっていた単語が引っかからなくなり,修正漏れが生じてしまう.

本来であれば fill-paragraph の挙動を修正するのが良いのかもしれないが,今回は単語の途中に一つ改行文字が入っていても正しく動作するような query-replace を作成することで対処した.

delete-if をつかっているので (require 'cl) が必要.

(defun query-replace-ignore-filling (from-string to-string &optional delimited start end)
  "Modified query-replace which also works correctly for filled paragraphs."
  (interactive
   (let ((common
          (query-replace-read-args
           (concat "Query replace"
                   (if current-prefix-arg " word" "")
                   (if (and transient-mark-mode mark-active) " in region" ""))
           nil)))
     (list (nth 0 common) (nth 1 common) (nth 2 common)
           (if (and transient-mark-mode mark-active)
               (region-beginning))
           (if (and transient-mark-mode mark-active)
               (region-end)))))
  (perform-replace
   (mapconcat 'identity
              (delete-if #'(lambda (s) (equal s ""))
                         (split-string from-string ""))
              "\\(\r?\n\\)?")
   to-string t t delimited nil nil start end))