Emacs で SQL 書く → リージョン選択(現在位置から先頭に移動して末尾に移動) → コピー
というのを何回も繰り返す場合、
- 手数が多くてだるい
- カーソルを移動させると、元の位置に戻るのがだるい
というあたりがだるかったので改善してみた。
完成形
(defun my-sql:query-beginning () (let (beg beg-para beg-sc) (save-excursion (backward-paragraph) (setq beg-para (+ (point) 1))) (save-excursion (when (search-backward-regexp ";\n" nil t) (setq beg-sc (+ (point) 2)))) (if beg-sc (max beg-para beg-sc) beg-para))) (defun my-sql:query-end () (let (end end-para end-sc) (save-excursion (forward-paragraph) (setq end-para (- (point) 1))) (save-excursion (when (search-forward-regexp ";\n" nil t) (setq end-sc (- (point) 1)))) (if end-sc (min end-para end-sc) end-para))) (defun my-sql:copy-current-query () "Copy current query." (interactive) (kill-ring-save (my-sql:query-beginning) (my-sql:query-end))) (global-set-key (kbd "C-M-h") 'my-sql:copy-current-query)
メモ
まず、今いる位置から動かずに1キーストロークで段落選択してコピーできるようにしてみた。mark-paragraph 使えば簡単。
(defun copy-current-query () "..." (interactive) (save-excursion (mark-paragraph) (kill-ring-save (region-beginning) (region-end))))
これの不満点:
- 選択範囲の先頭に余計な改行が付く … 困るわけではないが余計なので削りたい
- 選択範囲の末尾に余計な改行が付く … 最後のセミコロンの後に改行が付いていると、RDBMSクライアントのプロンプトにペーストしたときに即実行されてしまうのが嫌。最後の改行なしでペーストして、(場合によっては深呼吸などして)確認した後にエンター押下で実行させたい。ワンクッション置きたい。
(defun copy-current-query () "..." (interactive) (let (beg end) (save-excursion (backward-paragraph) (setq beg (+ (point) 1)) (forward-paragraph) (setq end (- (point) 1)) (kill-ring-save beg end))))
まだ不満な点:
選択の単位として段落を使うと、たとえば1行ずつのSQLをこのように並べて書いていて
select * from table1 limit 10; select * from table2 limit 10; select * from table3 order by created_at desc limit 10;
真ん中のクエリだけコピーしたいのに3行ともコピーされてしまう。1行ごとに改行入れればよいけど、ワンライナーのために無駄に行を消費したくない。
そこで、区切りとして前方、後方の ";\n" を探すようにしてみる。
セミコロン書かない+段落で見れば良いというケースもあるので、段落境界と比較して近い方を使うようにする。
(defun copy-current-query () "..." (interactive) (let (beg beg-para beg-sc end end-para end-sc) (save-excursion (backward-paragraph) (setq beg-para (+ (point) 1)) (forward-paragraph) (setq end-para (- (point) 1))) (save-excursion (if (search-backward-regexp ";\n" nil t) (setq beg-sc (+ (point) 2)) (setq beg-sc beg-para))) (save-excursion (if (search-forward-regexp ";\n" nil t) (setq end-sc (- (point) 1)) (setq end-sc end-para))) (setq beg (max beg-para beg-sc)) (setq end (min end-para end-sc)) (kill-ring-save beg end)))
大体いい感じになった。
選択範囲の始点・終点を求める部分は使いまわせそうなので、関数を抽出する。こうしておくと、たとえば、今いるクエリをフォーマットする場合なんかに流用できる。
関数が複数になったので名前空間的に my-sql: というプレフィックスを付けてみた。
;; 上の完成形と同じなのでコード省略
関数ごとに目的がはっきりしているし、良いと思う。
以下おまけ。自分用設定的なもの。
視覚的フィードバックが何もないと不安になるので、どこをコピーしたのか分かるように一定時間ハイライトさせる。
(一定時間だけ指定範囲をハイライトする Emacs Lisp | anobota を使用)
(require 'volatile-highlight) (defun my-sql:copy-current-query () "..." (interactive) (let ((beg (my-sql:query-beginning)) (end (my-sql:query-end))) (kill-ring-save beg end) (volatile-highlight beg end 1)))
フォーマット。
参考: (書きかけ)EmacsでSQLの整形(要Ruby) | anobota
(defun my-sql:format-currenty-query () "Format current query" (interactive) (shell-command-on-region (my-sql:query-beginning) (my-sql:query-end) (format "ruby %s" anbt-sql-formatter:formatter-path) nil t) (volatile-highlight (my-sql:query-beginning) (my-sql:query-end) 0.2)) (global-set-key (kbd "C-S-f") 'my-sql:format-currenty-query)