Hatena::Groupemacs

はてなグループEmacs@kiwanami

2011-08-09

Emacs Lisp の文字列操作まとめ

17:06

基本的には Emacs Lisp の Info を読むと何となくパーツはそろっていることは分かる。ただ、パーツが特殊だったりしてよく使い方を忘れるのでよく見る情報をメモ。

基本的情報源

以下の情報はローカルにブックマークするなどして即引けるようにしておくと便利。

Ruby との文字列操作対応表

基本的に破壊的操作は無し。

Ruby (String) Emacs Lisp 注釈
s = "abc" (setq s "abc") 変数にバインドするならlet使う方が一般的
s = x + y (concat x y)
s == x (equal s x) string-equal が正式
s % [x, y]
sprintf(s, x, y)
(format s x y)
[x, y, z].join(s) (mapconcat 'identity (list x y z) s) 単に結合するなら (apply 'concat (list x y z)) もアリ
s.capitalize (capitalize s)
s.capitalize!
s.center(x) (cfw:render-center x s) calfw.el 参照
s.chomp (regexp-in-string "[\n\r]+$" "" s)
s.chomp!
s.chop (substring s 0 -1) 改行文字の扱いが違う
s.chop!
s.clear (clear-string s)
s.concat(x)
s.count(x) ループで回すか、一時バッファを使って
re-search-forward で回数を数えるとか
s.crypt(x) rot13-string とか、 EPA とか
EzCrypt
s.delete!(x) (regexp-in-string x "" s) 破壊的には行えない
s.downcase (downcase s)
s.downcase!
s.each_byte {|x| ... } (loop for x across s do … ) 文字単位でループ、xは文字コード(数値)
文字コードからstringにするには char-to-string
s.each_line {|x| ... } (loop for x in (split-string s "[\n\r]") do … )
s.empty? (= 0 (length s)) nullチェックが必要
s.end_with?(x) (string-match (concat x "$") s) sに改行が入らないことが前提
s.gsub(x, y) (regexp-in-string x y s)
s.gsub!(x, y)
s.hex
s.to_i(16)
(string-to-number s 16)
s.include?(x) (string-match x s) もし見つかれば開始位置が返ってくる
s.index(x) (string-match x s)
s.insert(i, x) 一時バッファでやるか、自分でちぎってつなげるか
s.intern
s.to_sym
(intern s) シンボルテーブルに
登録したくなければ make-symbol
s.length
s.size
(length s) 文字数が返る
s.ljust(x) (cfw:render-left x s) calfw.el 参照
s.lstrip (regexp-in-string "^[ \t]+" "" s) sに改行が入らないことが前提
s.lstrip!
s.match(x)
x.match(s)
(string-match x s) マッチ結果はグローバルに保持している
match-string, match-beginning, match-end 等参照
s.next!
s.succ!
s.oct
s.to_i(8)
(string-to-number s 8)
s.partition(x) string-matchで自分でちぎる
s.replace(x)
s.reverse (apply 'string (reverse (append s nil)))
s.reverse!
s.rindex(x) 一時バッファで search-backward 等を使う
s.rjust(x) (cfw:render-right x s) calfw.el 参照
s.rpartition(x) string-matchで自分でちぎる
s.rstrip (regexp-in-string "[ \t]+$" "" s) sに改行が入らないことが前提
s.rstrip!
s.scan(x) { ... } 一時バッファで re-search-forward 等を使う
s[i]
s.slice(i)
(aref s i) 文字コード(数値)が返ってくる
文字コードからstringにするには char-to-string
s[i..-1]
s.slice(i..-1)
(substring s i)
s[i, l]
s.slice(i, l)
(substring s i (+ i l))
s[i..j]
s.slice(i..j)
(substring s i (1+ j))
s[i...j]
s.slice(i...j)
(substring s i j)
s.split(x) (split-string s x)
s.start_with?(x)*4 (string-match (concat "^" x) s) sに改行が入らないことが前提
s.strip (regexp-in-string "\\(^[ \t\n\r]+\\|[ \t\n\r]+$\\)" "" s) sに改行が入らないことが前提
s.strip!
s.sub(x, y) string-match を使って自分で初回を取ってくる
s.sub!(x, y)
s.swapcase
s.to_f (string-to-number s) 整数に見える場合は整数が返る
s.to_i (string-to-number s)
s.to_s (substring s 0)
s.tr!(x, y)
s.tr_s!(x, y)
s.unpack(x) bindatを使う
s.upcase (upcase s)
s.upcase!
Regexp.escape(s)
Regexp.quote(s)
(regexp-quote s)

※もし間違いを見つけたら教えてください。

表示幅について

format の桁指定("%5s" など)で表示幅のパディングが出来るが、あふれた文字列は切り落とされずに全部表示されてしまう。

指定幅で切り取りたい場合は、自分であらかじめ文字列の長さを見て切り落と(truncate)しておく必要がある。

切り落としには substring とかではなくて、全角文字などについても適切に幅を見てくれる truncate-string-to-width を使うと良い感じ。

文字列内の複雑な検索、置換など

replace-regexp-in-string を使えば大抵のことが出来る。関数も取れるので Ruby の gsub 相当も出来る。

ただ、複雑な正規表現をこねくり回したい場合は以下のように一時バッファを使う。

;; input に文字列が入っている
;; output に変換後の文字列を入れたい
(setq output
  (with-temp-buffer
    (insert input)
    (goto-char (point-min))
    (while (re-search-forward REGEXP nil t)
        ;; ここで match-string などを使って何かする
     )
    (buffer-string)))

テキストプロパティについて

HTMLDOM オブジェクトに独自属性を付加するように、自由に属性を入れることが出来る。 HTML とは違って1文字単位でプロパティが設定できるが、大抵はまとまった領域にプロパティを設定する。

テキスト属性を使えるようになると、かなり複雑なアプリケーションが作れるようになる(と思う)。

アプリケーションからの利用

テキストプロパティに必要な値をごっそり入れておき、キー操作でカーソル上の文字列からテキストプロパティを取ってきて必要なアクションを行うというような方法が一般的に行われている。大抵は、行全体や必要な領域全体にプロパティをつけておいて、キー操作で取得するときは行頭の1文字目のプロパティを見るということがよく行われている。

Dired や orgmode など、多くのプログラムがこのような設計になっている。しかしながら、場当たり的にテキストプロパティを使い始めると、テキストプロパティを舞台にしたスパゲティコードになるため、本格的に使う場合はあらかじめ構造化したデータを代表して入れておく等の工夫をした方がよいと考えている。

はまりポイントなど

属性は文字列の操作などでもそのままコピーされてしまうので、欲しくない場合は substring-no-properties などで消す。

属性の操作は破壊的なので、うっかり変えてはいけないところまで変えてしまうことがある。また、ソースコードリテラル文字列オブジェクトには属性の操作ができない。なので、外から来た文字列に対して属性を操作する場合は、文字列をコピーしておくと安全。

font-lock が有効なバッファで face に何か入れても速攻でクリアされてしまう。そんなときは font-lock-face に入れる。

テキスト属性のループパターン

バッファの中の特定のテキストプロパティについてループを書きたいときがある。そんなときは next-property-change または next-single-property-change などを使う。 Info の next-property-change にループのサンプルがある。

以下は face の付いている領域についてループするイディオム

(defun iterate-text-prop (prop func)
  (loop with pos = (point-min)
        for next = (next-single-property-change pos prop)
        for text-val = (and next (get-text-property next prop))
        while next do
        (when text-val
          (funcall func pos next text-val))
        (setq pos next)))

(iterate-text-prop 'face 
  (lambda (begin end val)
    (and val (message ">> %S : %S" val (buffer-substring-no-properties begin end)))))

N オーダーではあるけどもなかなか高速に動作するので、 O(N^2) のアルゴリズムでなければ問題なさそう。

2011-07-04

Emacs でドロップダウンリスト

19:07

emacs-devel に投稿した内容から。

Emacs上で、ドロップダウンもしくはポップアップして一覧から選択するインタフェースを実現するには、以下のものがある。

  • dropdown-list (dropdown-list.el)
  • popup-menu (mouse.el)
  • popup-menu* (popup.el)

各サンプルコードは後ろの方に掲載。

dropdown-list (dropdown-list.el)

yasnippet に付属。260行程度とコンパクト。APIもシンプル。

実装はオーバーレイテキストを使っているので、ターミナル環境でも使える。マウスでの選択は出来ない。

一方で、シンプルすぎて汎用的な目的には機能が若干足りない。特に、項目数が多いときは画面からはみ出てしまう。(一応専用バッファでのフォールバック機能があるみたい)

popup-menu (mouse.el)

標準でEmacsに付属。GUIポップアップメニューを出す。ターミナル環境では使えないが、マウスで選択が出来る。

通常のメニューと同じような構築をするので、Emacsのメニューをよく知っている人はすぐ使えるけども、知らない人にとってはちょっと複雑かもしれない。

popup-menu* (popup.el)

Auto-Complete で使われているもの。オーバーレイで描画しているため、ターミナル環境でも使える。マウスでの選択は出来ない。

コードは1000行ぐらいなので小さくはないけども、ポップアップメニューだけでなく、リッチな補完メニューとしての使用に耐えられるくらい機能がそろっている。

  • スクロールできるので項目が多くても大丈夫
  • インクリメンタルサーチで絞り込める
  • 入れ子のメニューも作れる
    • popup-menu も作れる
  • サマリー欄やドキュメント表示枠がある(Eclipse や VS で関数のヘルプが出るようなもの)

popup-menu* にマウスでの選択機能とかが標準で付けば一押しではないかと思っている。

;;; Simple popup menu comparing

(require 'dropdown-list)
(require 'popup)

(defvar ddtest-list 
  '("Dictaphone" "Dictaphone's" "dicta" "dicta's" "dictate" 
    "dictated" "dictates" "dictating" "dictation" "dictation's"
    "dictations" "dictator" "dictator's" "dictatorial" "dictators"
    "dictatorship" "dictatorship's" "dictatorships" "diction"
    "diction's" "dictionaries" "dictionary" "dictionary's" "dictum"
    "dictum's" "dictums"))


(defun ddtest-show-dropdown-list ()
  (interactive)
  (let ((num (dropdown-list ddtest-list)))
    (and num (insert (nth num ddtest-list)))))

(defun ddtest-show-x-popup-menu ()
  (interactive)
  (let* ((menu-map (make-sparse-keymap "Popup Test")) val)
    (mapc (lambda (x) 
            (lexical-let ((x x))
              (define-key menu-map 
                (vector (intern x))
                (cons x (lambda () (interactive) (insert x))))))
          ddtest-list)
    (popup-menu menu-map)))

(defun ddtest-show-popup-menu ()
  (interactive)
  (let ((val (popup-menu*
              (mapcar (lambda (x) 
                        (popup-make-item x :value x))
                      ddtest-list)
              :scroll-bar t)))
    (and val (insert val))))

;; Do it.

;; (ddtest-show-dropdown-list)
;; (ddtest-show-x-popup-menu)
;; (ddtest-show-popup-menu)

company-mode でのポップアップメニュー

@khiker さんから company-mode の情報を教えてもらいました。情報ありがとうございます!

company-modeは、 Auto-Complete のような、プラグイン的なシステムによって拡張可能な、高機能補完プログラムです。このプログラムはオーバーレイによる補完メニューを表示します。

元々補完専用なのですが、無理矢理メニューのように使う場合は以下のようになると思います。

(defun company-menu-backend (command &optional arg &rest ignored)
  (case command
    ('prefix "")
    ('candidates ddtest-list)
    ('sorted t)))

(defun company-menu ()
  (interactive)
  (company-begin-backend 'company-menu-backend
                         (lambda (item) (message item))))

;; (company-mode) ; マイナーモードON
;; (company-menu) ; ←メニュー表示

大量データ時のスクロール、絞り込み、ドキュメント表示など、 popup-menu* と同様な機能を持っています。

マウスでクリックできるところなど、機能的にはかなり頑張っています。余談ですがcompany-backendの拡張の仕方はメッセージパッシング的で面白いです。

ちょっと残念なのは、ポップアップメニューとして機能を切り出せないところとか、日本語幅を考慮してないようなので日本語を含むと表示が少し崩れてしまうところでしょうか。

2011-06-16

Emacs Lisp でのオブジェクトシステム的ないろいろ

10:20

今まで発見したもの。多分探せばもっとありそう。それぞれの詳細とか違いについてはまた今度書くかも。

  • defstruct
    • 標準添付。clパッケージのマクロ
    • 単純に型や入れ物やアクセサ、デフォルト値を定義。簡易継承あり。メソッドは必要なら自分で作る。 (2011/08/10 訂正)
    • 単純な用途には便利。
  • eieio
  • luna.el
    • FLIMに含まれる
    • クラス定義、継承とメッセージ的なOOP
    • 435行
    • ドキュメントは見たこと無いが、ソース見ると何となく分かる
    • FLIM, SEMI などのドキュメント構成をコンポーネントとして定義している感じ
  • widget.el
  • button.el
    • 標準添付
    • 200行ぐらい
    • クラス(ボタン)定義、継承、メッセージ(GUIイベント)受信
    • help-modeのボタンたちを定義するコンポーネント。internしてないシンボルを自前で管理。

とりあえずデバッガが使いづらくなると困る。

eieioはちょっと大げさな気がするという感触。でも使ってみたら思いの外便利かもしれない。

GUIにはやっぱりコンポーネントが必要だし、MVCやろうとするとMをオブジェクトでまとめたくなる。

自分の所でもいろいろ作ったけども、どれが良かったのかまだ見極められない。

  • skype.el, id-manager.el
  • e2wm.el
    • 普通に関数とdefstructで構築してみた。lunaかeieio使えば良かったかも。
  • calfw.el(次期版)
    • カプセル化をやりたくてmodelを関数でいろいろ頑張った。コードが多くなりすぎた。

やっぱり独自オブジェクトシステムはコンパクトで適材適所で良いときもあるけど、ありものを使うのも考えた方が良いかなと、いろいろやってきた思った。

2011-06-15

Emacs Lisp でバイナリ読み込み

23:16

以前twitterで紹介したものの再掲。

M-x twic で指定した twitter-ID のアイコンをEmacsバッファの中に表示する。こんな感じ。

twitter icon on Twitpic

Emacsでの解析を簡単にするためにpngppmに変換した。

bindat-unpack で「unpack」できる。構造体っぽいものであれば楽にできるのかもしれない。

ネットワーク経由でStreamっぽく使いたいけど試してない。

2011-06-08

Emacsの日時形式・変換まとめ

01:43

図にまとめてみた。

(クリックすると大きくなります)

情報源

  • Info など
  • calendar ソースなど (lisp/calendar, org)
  • howm ソースなど (calfw)

間違いや、追加すると便利な情報がありましたら教えてください。

その他参考