emacs 扩展 ido

scinart posted @ 2013年8月04日 23:47 in elisp with tags emacs ido , 3361 阅读

我用的是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交互而不是另起炉灶),费了我半天劲啊。
 


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter