emacs 扩展 ido
我用的是Emacs 24.2, (Windows操作系统不要鄙视我啊) 其他较高级的版本都理应类似。
如果你有同样的问题并且有好法,还望留言告知啊。
今天遇到一个问题是,在用ido-find-file的时候,这个文件适合用外部程序打开。(比如是个.doc什么的) 以前的做法是C-d进入文件夹,search那个文件,然后用我一个常见的
(w32-shell-execute 1 (dired-replace-in-string "/" "\\" (dired-get-filename)))
打开,然而今天我想为什么不能在C-x C-f的状态下用别的快捷键直接调用外部函数呢。
于是开始查。
C-x C-f下用\\[describe-mode]查看,为InactiveMinibuffer mode
再用\\[describe-bindings]查看,有以下等等
Major Mode Bindings:
key binding
--- -------
C-@ ido-restrict-to-matches
C-a ido-toggle-ignore
C-b ido-magic-backward-char
C-d ido-magic-delete-char
C-e ido-edit-input
C-f ido-magic-forward-char
......
算了,先看看RET干了啥吧。
RET runs the command ido-exit-minibuffer, which is an interactive
compiled Lisp function in `ido.el'.
(defun ido-exit-minibuffer ()
"Exit minibuffer, but make sure we have a match if one is needed."
(interactive)
(if (and (or (not ido-require-match)
(if (memq ido-require-match '(confirm confirm-after-completion))
(if (or (eq ido-cur-item 'dir)
(eq last-command this-command))
t
(setq ido-show-confirm-message t)
nil))
(ido-existing-item-p))
(not ido-incomplete-regexp))
(exit-minibuffer)))
and部分总是真,不管了,模仿出来了个能工作的就行。
exit-minibuffer is an interactive compiled Lisp function in
`minibuffer.el'.
(defun exit-minibuffer ()
"Terminate this minibuffer argument."
(interactive)
(setq deactivate-mark nil)
(throw 'exit nil))
我擦,这函数居然跟ido.el没干什么关系,还出了一个状况。我上哪接去啊?
没办法只能看C-x C-f干了啥了。
C-x C-f runs the command ido-find-file, which is an interactive
compiled Lisp function in `ido.el'.
(defun ido-find-file () ;; 注释省略
(interactive) ;; 活都给ido-file-internal 来做了,还传进去了一个ido-default-file-method
(ido-file-internal ido-default-file-method))
看看ido-file-internal干了些啥:
ido-file-internal is a Lisp function in `ido.el'.
(ido-file-internal METHOD &optional FALLBACK DEFAULT PROMPT ITEM
INITIAL SWITCH-CMD)
Not documented.
中间edebug N步省略 line 2349:
(unless filename
(setq ido-saved-vc-hb vc-handled-backends)
(let ((minibuffer-completing-file-name t))
;; 就是从这儿读的minibuffer, 返回值是文件名,如果你输入的文件不存在也就返回你输入的文件名
(setq filename (ido-read-internal item
(or prompt "Find file: ")
'ido-file-history
(and (eq method 'alt-file) buffer-file-name)
(confirm-nonexistent-file-or-buffer) initial))))
因为我不知道exit-minibuffer那个throw exit是怎么回事,所以我决定继续使用ido-file-internal接下来的代码
(cond
((eq ido-exit 'fallback)
;; Need to guard setting of default-directory here, since
;; we don't want to change directory of current buffer.
(let ((default-directory ido-current-directory)
(read-file-name-function nil))
(setq this-command (or fallback 'find-file))
(run-hook-with-args 'ido-before-fallback-functions this-command)
(call-interactively this-command)))
...)
这是个大cond, 如果按回车,会符合最后一个条件,也就是打开文件。
但这里的精华是(在C-x C-f C-f时发现的)
(defun ido-fallback-command ()
"Fallback to non-ido version of current command."
(interactive)
(let ((i (length ido-text)))
(while (> i 0)
(push (aref ido-text (setq i (1- i))) unread-command-events)))
(setq ido-exit 'fallback)
(exit-minibuffer))
即,在执行这个函数之后,之前那个cond就会落在第一个分支里,在这里执行fallback命令,默认是ido-default-file-method 但是由于源代码里是这样写的:(setq this-command (or fallback 'find-file) 而我没不都想改变默认是find-file 这一行为,看来把这段代码变一下还是相对活力的一种方式。
解决方案:在执行(or fallback 'find-file)之中加一次对外部函数的检查,以便我们在执行fallback的时候能执行任意我们想要的外部函数。
下面高亮的为变动部分。
---------------------------------------------------------------------
(defvar ido-fallback-function nil "The fallback function that will be explicitly check and can be externally modified
this variable is introduced to enhance ido-find-file functionality
search (cond ... ((eq ido-exit 'fallback) ... )) to see where it's used.
2013-08-04 Sunday 19:35:45 by Scinart")
(defun ido-file-internal (method &optional fallback default prompt item initial switch-cmd)
;; Internal function for ido-find-file and friends
(unless item
(setq item 'file))
(let ((ido-current-directory (ido-expand-directory default))
(ido-context-switch-command switch-cmd)
ido-directory-nonreadable ido-directory-too-big
filename)
(if (or (not ido-mode) (ido-is-slow-ftp-host))
(setq filename t
ido-exit 'fallback)
(setq ido-directory-nonreadable
(ido-nonreadable-directory-p ido-current-directory)
ido-directory-too-big
(and (not ido-directory-nonreadable)
(ido-directory-too-big-p ido-current-directory))))
(when (and (eq item 'file)
(or ido-use-url-at-point ido-use-filename-at-point))
(let (fn d)
(require 'ffap)
;; Duplicate code from ffap-guesser as we want different
;; behavior for files and URLs.
(cond
((with-no-warnings
(and ido-use-url-at-point
ffap-url-regexp
(ffap-fixup-url (or (ffap-url-at-point)
(ffap-gopher-at-point)))))
(setq ido-exit 'ffap
filename t))
((and ido-use-filename-at-point
(setq fn (with-no-warnings
(if (eq ido-use-filename-at-point 'guess)
(ffap-guesser)
(ffap-string-at-point))))
(not (string-match "^http:/" fn))
(let ((absolute-fn (expand-file-name fn)))
(setq d (if (file-directory-p absolute-fn)
(file-name-as-directory absolute-fn)
(file-name-directory absolute-fn))))
(file-directory-p d))
(setq ido-current-directory d)
(setq initial (file-name-nondirectory fn))))))
(let (ido-saved-vc-hb
(vc-handled-backends (and (boundp 'vc-handled-backends) vc-handled-backends))
(ido-work-directory-index -1)
(ido-work-file-index -1)
(ido-find-literal nil))
(unless filename
(setq ido-saved-vc-hb vc-handled-backends)
(let ((minibuffer-completing-file-name t))
(setq filename (ido-read-internal item
(or prompt "Find file: ")
'ido-file-history
(and (eq method 'alt-file) buffer-file-name)
(confirm-nonexistent-file-or-buffer) initial))))
;; Choose the file name: either the text typed in, or the head
;; of the list of matches
(cond
((eq ido-exit 'fallback)
;; Need to guard setting of default-directory here, since
;; we don't want to change directory of current buffer.
(let ((default-directory ido-current-directory)
(read-file-name-function nil))
(setq this-command (or fallback ido-fallback-function 'find-file))
(run-hook-with-args 'ido-before-fallback-functions this-command)
(call-interactively this-command)
(setq ido-fallback-function nil)))
((eq ido-exit 'switch-to-buffer)
(ido-buffer-internal
(if (memq method '(other-window other-frame)) method ido-default-buffer-method)
nil nil nil ido-text))
((eq ido-exit 'insert-buffer)
(ido-buffer-internal 'insert 'insert-buffer "Insert buffer: " nil ido-text 'ido-enter-insert-file))
((eq ido-exit 'dired)
(dired (concat ido-current-directory (or ido-text ""))))
((eq ido-exit 'ffap)
(find-file-at-point))
((eq method 'alt-file)
(ido-record-work-file filename)
(setq default-directory ido-current-directory)
(ido-record-work-directory)
(find-alternate-file filename))
((memq method '(dired list-directory))
(if (equal filename ".")
(setq filename ""))
(let* ((dirname (ido-final-slash (concat ido-current-directory filename) t))
(file (substring dirname 0 -1)))
(cond
((file-directory-p dirname)
(ido-record-command method dirname)
(ido-record-work-directory dirname)
(funcall method dirname))
((file-directory-p ido-current-directory)
(cond
((file-exists-p file)
(ido-record-command method ido-current-directory)
(ido-record-work-directory)
(funcall method ido-current-directory)
(if (eq method 'dired)
(with-no-warnings
(dired-goto-file (expand-file-name file)))))
((string-match "[[*?]" filename)
(setq dirname (concat ido-current-directory filename))
(ido-record-command method dirname)
(ido-record-work-directory)
(funcall method dirname))
((y-or-n-p (format "Directory %s does not exist. Create it? " filename))
(ido-record-command method dirname)
(ido-record-work-directory dirname)
(make-directory-internal dirname)
(funcall method dirname))
(t
;; put make-directory command on history
(ido-record-command 'make-directory dirname))))
(t (error "No such directory")))))
((eq method 'write)
(ido-record-work-file filename)
(setq default-directory ido-current-directory)
(setq filename (concat ido-current-directory filename))
(ido-record-command 'write-file filename)
(add-to-history 'file-name-history filename)
(ido-record-work-directory)
(write-file filename t))
((eq method 'read-only)
(ido-record-work-file filename)
(setq filename (concat ido-current-directory filename))
(ido-record-command fallback filename)
(ido-record-work-directory)
(run-hook-with-args 'ido-before-fallback-functions fallback)
(funcall fallback filename))
((eq method 'insert)
(ido-record-work-file filename)
(setq filename (concat ido-current-directory filename))
(ido-record-command
(if ido-find-literal 'insert-file-literally 'insert-file)
filename)
(add-to-history 'file-name-history filename)
(ido-record-work-directory)
(insert-file-1 filename
(if ido-find-literal
#'insert-file-contents-literally
#'insert-file-contents)))
(filename
(ido-record-work-file filename)
(setq filename (concat ido-current-directory filename))
(ido-record-command 'find-file filename)
(add-to-history 'file-name-history filename)
(ido-record-work-directory)
(ido-visit-buffer (find-file-noselect filename nil ido-find-literal) method))))))
(小提示,由于ido.el有已经编译过的文件,所以要么重新编译要么重新定义函数噢)
为了让这个外部函数工作还需要做以下工作
(add-hook 'ido-minibuffer-setup-hook 'ido-my-keys)
(defun ido-my-keys ()
"My Keybindings for ido
especially for extending ido-find-file functionality
2013-08-04 Sunday 17:25:03"
(define-key ido-completion-map (kbd "<return>") 'ido-exit-minibuffer) ;; for find-file
(define-key ido-completion-map (kbd "RET") 'ido-magic-open-using-w32))
快捷键什么的都可以自己定义了,下面请看我如何自定义函数并与ido的minibuffer结果交互:
(defun ido-magic-open-using-w32 ()
"This should be used when ido-minibuffer is active"
(interactive)
;; (let ((i (length ido-text)))
;; (while (> i 0)
;; (push (aref ido-text (setq i (1- i))) unread-command-events)))
(setq ido-exit 'fallback)
(setq ido-fallback-function
'(lambda () (interactive)
(let ((dir ido-current-directory)
(file (car ido-matches)))
(w32-shell-execute 1 (concat dir file)))))
(exit-minibuffer))
其中 ido-current-directory是minibuffer返回文件的文件夹,ido-matches是minibuffer返回的文件名,我写的函数功能是用windows默认应用程序打开,关于其他的变量,请到这两个变量的附近自行查找,根据我的实验,有文档的大概都可以用。
结论:就为了一个外部函数与ido交互(还是与默认的ido-find-file交互而不是另起炉灶),费了我半天劲啊。