2007/07/18
巨大なディレクトリ構造でもフラットに find-file する
find-file でいちいちディレクトリを補完するのが面倒なので以下のツール( + elisp )を書きました。ありそうなアイデアですが探してもなかったので自作しました。
できること
- locate の部分的適用 + α
- 巨大なディレクトリ構造でもフラットに find-file ができる
必要なプログラム
- perl
- slocate
使い方(コマンドライン)
あらかじめ path を /usr/bin/ あたりにインストールしておいてください。
検索
クエリ文字列に一致するファイルのフルパスを得ることができます。 PATH があるディレクトリ配下ならどこからでも利用できます。
% pwd /path/to/ % ls PATH hoge1 foo/ % path hoge /path/to/hoge1 /path/to/foo/hoge2
zsh を使ってる場合は
% less =path hoge
で一発で hoge を less することができます。
その他
- -e オプションでクエリ対象の排除ができます
- -E で .svn とか .cvs をクエリ対象から排除できます
- -r で正規表現が使えます
- -i で case-insensitive になります
使い方( Emacs )
初期設定
(autoload 'path-find-file "path" nil t) (global-set-key "\C-xp" 'path-find-file)
M-x path-find-file
PATH から取得したディレクトリ・ファイル名をミニバッファで補完するだけで find-file できます。
メモ
- 処理が強引かつ適当なのはわかってる
- slocate わけわからんから許して
- elisp 始めてだから許して
- completin-read 使わないバージョンを作るべき( read-string など)
- バグだらけ
本体( perl スクリプトと elisp )
path:
#!/usr/bin/perl
use strict;
use Getopt::Long;
use File::Basename;
my $print;
my $list;
my $update;
my $icase;
my $regex;
my @excludes = ();
my $exclude_vc;
GetOptions('print' => \$print,
'list' => \$list,
'update' => \$update,
'icase' => \$icase,
'regex' => \$regex,
'exclude=s' => \@excludes,
'exclude-vc|E' => \$exclude_vc) or exit(1);
if ($exclude_vc) {
push @excludes, '\.svn', '\.cvs';
}
if ($print) {
my ($dir) = find_database();
print "$dir\n";
} elsif ($update) {
my ($dir, $name) = find_database();
my $database = join_path($dir, $name);
my @args = ('slocate', '-U', $dir, '-o', $database, '-l', '0');
shift @args, '-e', join(',', @excludes) if @excludes;
system(@args);
} else {
my ($dir, $name) = find_database();
my $database = join_path($dir, $name);
if (-e $database) {
my @args = ('slocate', '-d', $database);
push @args, '-i' if $icase;
push @args, '-r' if $regex;
if ($list) {
push @ARGV, '/';
my $cmd = join(' ', @args, @ARGV);
my @list = grep { !match_excludes($_) } map { basename $_ } split /\n/, `$cmd`;
my %seen = ();
for (@list) {
next if $seen{$_};
$seen{$_} = 1;
print "$_\n";
}
} elsif (@ARGV) {
my $cmd = join(' ', @args, @ARGV);
for (split /\n/, `$cmd`) {
next if match_excludes($_);
print "$_\n";
}
} else {
die "No query string";
}
} else {
die "Cannot find database: $database";
}
}
sub match_excludes {
my $str = shift;
for (@excludes) {
return 1 if $str =~ /$_/;
}
return 0;
}
sub unique {
my %seen = ();
$seen{$_} = 1 for @_;
return \(keys %seen);
}
sub split_path {
return split '/', shift;
}
sub join_path {
return join '/', @_;
}
sub find_database {
my $name = 'PATH';
my $pwd = `pwd`;
chomp $pwd;
my @parts = split_path($pwd);
while (@parts) {
my $dir = join_path(@parts);
if (-e join_path($dir, $name)) {
return ($dir, $name);
}
pop @parts;
}
return ($pwd, $name);
}
path.el:
;;; path.el
;; Author: MATSUYAMA Tomohiro
;; Version: 1.0
;; Licence: GPLv2
;;; Code
(defconst path-db-name "PATH")
(defconst path-buffer-name "*PATH*")
(defvar path-do-cache t)
(defvar path-hashtab (make-hash-table :test 'equal))
(defvar path-db-rootdir nil)
(defvar path-options "-E")
(defvar path-saved-buffer nil)
(defvar path-select-mode-map (make-sparse-keymap))
(define-key path-select-mode-map "n" 'next-line)
(define-key path-select-mode-map "p" 'previous-line)
(define-key path-select-mode-map "q" 'path-quit-selection)
(define-key path-select-mode-map "\C-m" 'path-select-path)
(defun path-clear-hashtab ()
(clrhash path-hashtab))
(defun path-get-buffer ()
(get-buffer path-buffer-name))
(defun path-call-process (&rest args)
(let ((buffer (path-get-buffer))
(saved-buffer (current-buffer))
result)
(set-buffer (progn
(when (null buffer)
(setq buffer (generate-new-buffer (generate-new-buffer-name path-buffer-name)))
(buffer-disable-undo buffer))
buffer))
(when path-db-rootdir
(cd path-db-rootdir))
(kill-region (point-min) (point-max))
(setq result
(if (= 0 (apply 'call-process "path" nil t nil args))
(buffer-substring (point-min) (1- (point-max)))
nil))
(set-buffer saved-buffer)
result))
(defun path-find-db-rootdir (dir)
(setq dir (directory-file-name dir))
(if dir
(if (file-exists-p (concat (file-name-as-directory dir) path-db-name))
dir
(progn
(let ((parent (file-name-directory dir)))
(if (equal dir parent)
nil
(path-find-db-rootdir parent)))))
nil))
(defun path-visit-db-rootdir ()
(interactive)
(setq path-db-rootdir (path-find-db-rootdir (expand-file-name default-directory))))
(defun path-list-names ()
(path-visit-db-rootdir)
(let ((lst (gethash path-db-rootdir path-hashtab)))
(when (null lst)
(setq lst (split-string (path-call-process "-l" path-options) "\n"))
(when path-do-cache
(puthash path-db-rootdir lst path-hashtab)))
lst))
(defun path-goto-path (name)
(path-visit-db-rootdir)
(let ((str (path-call-process path-options "-r" (format "'/%s$'" name))))
(when str
(let* ((lst (split-string str "\n"))
(cnt (safe-length lst)))
(cond
((= cnt 1)
(find-file (car lst)))
((> cnt 1)
(switch-to-buffer (path-get-buffer))
(path-select-mode)))))))
(defun path-select-mode ()
(interactive)
(use-local-map path-select-mode-map)
(setq major-mode 'path-select-mode
mode-name "Path-Select")
(goto-char (point-min))
(run-hooks 'path-select-mode-hook))
(defun path-quit-selection ()
(interactive)
(switch-to-buffer path-saved-buffer))
(defun path-select-path ()
(interactive)
(setq path-saved-buffer (current-buffer))
(find-file (buffer-substring (point-at-bol) (point-at-eol))))
;;;###autoload
(defun path-find-file ()
(interactive)
(setq input (completing-read "File file: " (path-list-names)))
(when input
(path-goto-path input)))
- Category(s)
- emacs
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/flat-find-file/tbping