Emacs
emacs-lisp
テキストエディタ

Emacsでカーソル位置の相対パスのファイル名を他のバッファのディレクトリを基準に探して開く方法

相対パスのファイル名をコピペしたファイルが別ディレクトリにあっても、次の順序でがんばって探すので、ファイルが簡単に開けるようになります。

  1. フルパスだったらそのまま開く
  2. 現在のディレクトリを基準に探す
  3. 現在のディレクトリから見て先祖のディレクトリに .git があればそのディレクトリからの相対パスとして探す
  4. 直近で開いているバッファを順に切り替えながら、そのディレクトリを基準に 2 と 3 を試す

dashs という便利ライブラリを利用しているので M-x package-list-packages からのインストールが必要です。

(require 'dash)
(require 's)

(defun my-file-jump ()
  "カーソル下のファイルに飛ぶ。なかったら他のバッファのディレクトリを基点に探す
↓確認用
~/.emacs.d/init.el
~/.emacs.d/init.el:
~/.emacs.d/init.el:2
~/.emacs.d/init.el:3:
~/.emacs.d/init.el:3:in
~/.emacs.d/init.el(5)
~/.emacs.d/init.el (3 hit)       ; この数字は行番号ではない
./spec/models/user_spec.rb:6:in  ; がんばって探す
./spec/models/xxxx_spec.rb:6:in  ; がんばって探すがファイルがない
"
  (interactive)
  (let ((thing-at-point-file-name-chars "-~/[:alnum:]_.${}#%,") ; 後ろのコロンまで拾ってしまうためコロンだけ除去
        original-path path line git-root vpath)

    ;; とりあえずファイル名を拾う
    (setq original-path (thing-at-point 'filename))
    (unless original-path (error "?"))
    (setq original-path (s-chop-prefix "./" original-path))
    (setq path original-path)

    ;; 後ろに行番号らしきものがあれば拾う
    (save-excursion
      (end-of-thing 'filename)
      (unless (= (following-char) (string-to-char " "))
        (when (re-search-forward "[^[:digit:]]*\\([[:digit:]]+\\)" (line-end-position) t)
          (setq line (buffer-substring-no-properties (match-beginning 1) (match-end 1))))))

    ;; ファイルがなければ .git からの相対パスとみなす
    (unless (file-exists-p path)
      (setq git-root (locate-dominating-file default-directory ".git"))
      (when git-root
        (setq path (concat git-root original-path))))

    ;; それでもなければバッファリストから探す
    (unless (file-exists-p path)
      (catch 'loop
        (--each (-uniq (--map (with-current-buffer it default-directory) (buffer-list)))
          (progn
            ;; カレントからの相対パス
            (setq vpath (concat it original-path))
            (when (file-exists-p vpath)
              (setq path vpath)
              (throw 'loop nil))
            ;; .git からの相対パス
            (setq git-root (locate-dominating-file it ".git"))
            (when git-root
              (setq vpath (concat git-root original-path))
              (when (file-exists-p vpath)
                (setq path vpath)
                (throw 'loop nil)))))))

    (unless (file-exists-p path)
      (error "ファイルが見つかりません: %s" original-path))

    ;; ファイルが見つかったので
    (find-file path)
    (if line
        (goto-line (string-to-int line)))
    (delete-other-windows)              ; 他のWindowを消す (お好みで)
    (recenter-top-bottom)               ; 画面中央に配置   (お好みで)
    ))

キーバインドは C-j を無効にして C-j C-j に私は割り当てています。これもお好みです。

(global-unset-key "\C-j")
(global-set-key "\C-j\C-j" 'my-file-jump)