endに対応する行をミニバッファへ表示するマイナーモードを作ってみた

マイナーモードのメインの部分作ってから, ruby-modeの関数調べて気付いた.
車輪の再発明に近いことしてる.
ruby-modeの関数を利用しとけばよかったんじゃんorz
まあいいや, はじめてマイナーモード作ってみて色々な発見があったから.
色々問題出るかもしれないから, 使ってみようって人が居たらそこらへん覚悟して下さい.



ruby-modeにおいて, カーソル行のendに対応する行をミニバッファへ表示するモード.
例えば, このソースの場合,

class ClassTest
  hogehoge
  def DefTest
    foobar
  end
end              <---

「<--」の行にカーソルがあったとき,
ミニバッファに「1: class ClassTest」と行番号とともに表示してくれる.



使用法
下のソースを, ruby-block.elみたいな名前で保存. ロードパスの通った所へ置く.
.emacsへ「(require 'ruby-block)」と書く.
この式を評価するなり, Emacsを再起動するなりする(再起動した方がいいかも).
ruby-modeが起動しているRubyのソース上で, M-x ruby-show-block-modeとする.
もしくは, .emacsへ(ruby-show-block-mode t)と書く.



作成には, slime-autodoc-mode, eldoc-mode, show-paren-modeを参考にした.
マイナーモードはじめて作ったから, 結構ナンセンスなことやってる部分があると思う.
もしかしたら(いや, もしかしなくても), もっと簡単にできるんじゃないかと.
最初何処でもこのマイナーモード, オンになっちゃったからslime-autodoc-modeを参考にけっこう強引にruby-modeだけで有効になるようにしてる.
作った感想は, マイナーモード作成ってたいへん.
なんか色々作り方があるみたいで.
define-minor-modeを使ったり(show-paren-modeがそうだった), 普通にdefunでやったり(手元の本がそうやってた).

追記(0113)

不具合ハッケーン.
やっぱりあった(ノロ`)
「end」に対応するものが「do」のとき, 正規表現に一致しないから,
上手く対応を見つけられてない.
時間ができたら修正します. 修正方法まだ思い付いてないけど.
とりあえず, その不具合あっても良いって人だけ使ってください(すいません).

追記(0113その2)

なんかふと思いついて, 正規表現いじってみた.

"^[ \t]*\\(\\(end\\|for\\|while\\|until\\|if\\|class\\|module\\|case\\|unless\\|def\\|begin\\)\\|[^#\n]*[) \t]\\(do\\)\\)"

正規表現をこれに置き換えると上手くいくかもしれない.
もちろん, これに置き換えたことが原因でまた不具合が起きるかもしれないけど.

追記(0117)

別エントリにて, 更新.

とりあえず暇だったし何となく始めたブログ - ruby-block.el更新


対応する文字の背景の色を変えたりできたらいいなと考えていたり.
endがあるだけで, 対応するのが無いときは, ちゃんと何も出力しないようになってるはず. たぶん.

;;; ruby-block.el --- highlight matching block

;; Copyright (C) 2007  khiker

;; Author: khiker <khiker.mail+memo@gmail.com>
;; Keywords: languages, faces

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; 使用法
;; (require 'ruby-block)
;; M-x ruby-show-block-mode
;; もしくは, .emacsに以下を記述
;; (ruby-show-block-mode t)

;;; Code:

(defvar ruby-show-block-idle-delay 0.50 "")

(defvar ruby-show-block-keyword-list
  (list "end" "for" "while" "until" "if" "class" "module" "case" "unless" "def" "begin" "do")
  "ハイライト対象となるキーワード.")

(defvar ruby-show-block-keyword-regex
  "^[ \t]*\\(\\(end\\|for\\|while\\|until\\|if\\|class\\|module\\|case\\|unless\\|def\\|begin\\)\\|[^#\n]*[) \t]\\(do\\)\\)"
;  "^[ \t]*\\(end\\|for\\|while\\|until\\|if\\|class\\|module\\|case\\|unless\\|def\\|begin\\|do\\)"
  "対応を探す際に利用する正規表現.")

(defvar ruby-show-block-idle-timer nil "")

;; モード変数
(defvar ruby-show-block-mode nil)
;(make-variable-buffer-local 'ruby-show-block)

(defun ruby-show-block-mode (&optional arg)
  "Rubyでendに対応するキーワードのある行の行番号とその行全体をミニバッファ上に表示するマイナーモード."
  (interactive "P")
  ;; モード変数の設定
  (cond
   ((< (prefix-numeric-value arg) 0)
    (setq ruby-show-block-mode nil)
    )
   (arg
    (setq ruby-show-block-mode t))
   (t
    (setq ruby-show-block-mode (not ruby-show-block-mode))
    )
   )
  ;; タイマーのスタート
  (if ruby-show-block-mode
      (progn
        (ruby-show-block-start-timer)
        )
    (ruby-show-block-stop-timer))
  )

(defun ruby-show-block-start-timer ()
  "ruby-show-block-idle-delay秒毎に, カーソル行のendに対応する行を表示するタイマーをスタートする."
  (when ruby-show-block-idle-timer
    (cancel-timer ruby-show-block-idle-timer))
  (setq ruby-show-block-idle-timer
        (run-with-idle-timer ruby-show-block-idle-delay t 'ruby-show-block-hook)))

(defun ruby-show-block-stop-timer ()
  "タイマーを停止する."
  (when ruby-show-block-idle-timer
    (cancel-timer ruby-show-block-idle-timer)
    (setq ruby-show-block-idle-timer nil)))

(defun ruby-show-block-hook ()
  "major-modeがruby-modeのときだけ実行するようにする."
  (when (eq major-mode 'ruby-mode)
    (condition-case err
        (ruby-show-block-function)
      (error
       (setq ruby-show-block-mode nil)
       (message "Error: %S; ruby-show-block-mode now disabled." err)))))

;; 実際に処理をする関数.
(defun ruby-show-block-function ()
  (let ((current
         (car (member (current-word) ruby-show-block-keyword-list)))
        )
    (cond
     ;; キーワードでは無かった場合.
     ((null current)
      '())
     ;; キーワードが「end」だった場合.
     ((string= "end" current)
      (let (wordpoint line-begin line-end line-string line-num)
        (setf wordpoint
              (save-excursion
                ;; endに対応するキーワードのポイントを返す.
                (do ((count 1)          ; doの返戻値.
                     (check 1))         ; 1かnilか. 終了条件判定.
                    ((null check) count)
                  ;; 次のような場合において, 「endのうしろ」(^)の位置にカーソルがあった場合,
                  ;;  while
                  ;;   ... ... ...
                  ;;  end
                  ;;     ^
                  ;; 検索において, endを含めてしまうことを抑止するために, ポイントを行頭に移動させる.
                  (beginning-of-line)
                  (if (null (re-search-backward ruby-show-block-keyword-regex (point-min) t 1))
                      ;; バッファの先頭に到達してしまった.
                      (progn
                        (setf count -1)  ; 返戻値を異常終了を示す値へ.
                        (setf check nil) ; 繰り返しを終了へ.
                        )
                    (if (string= (match-string 1) "end")
                        ;; マッチしたものが「end」.
                        (setf count (1+ count))
                      ;; マッチしたのものが「end」以外のキーワード.
                      (setf count (1- count))
                      ))
                  ;; その結果, countが0ならば, 終了へ.
                  (if (= count 0)
                      (progn
                        (setf count (match-beginning 1))
                        (setf check nil) ;繰り返しを終了へ.
                        ))
                  ))
              )
        ;; ポイントのある行全体を取得し, ミニバッファへ表示する.
        (if (> wordpoint 0)
            (save-excursion
              (goto-char wordpoint)
              (beginning-of-line)
              (setf line-begin (point))
              (end-of-line)
              (setf line-end (point))
              (message "%d: %s" (line-number-at-pos) (buffer-substring line-begin line-end))
              ))
        ))
     ;; 「end」以外のキーワードだった場合.
     (t
      '()
      ))
    )
  )

(provide 'ruby-block)
;;; ruby-block.el ends here