url ファイルが D&D されたら URL を挿入する

http://xyzzy.s53.xrea.com/wiki/index.php?cmd=read&page=tips%2FURL%A5%B7%A5%E7%A1%BC%A5%C8%A5%AB%A5%C3%A5%C8%A4%F2D%A1%F5D%A4%B5%A4%EC%A4%BF%A4%E9URL%A4%F2%C1%DE%C6%FE%A4%B9%A4%EBインスパイヤされました。
今回の改造にあたって加えられた新たなオリジナリティは以下。

  • *insert-url-string-func-alist* で挿入する文字列を生成するようにしました。
  • *before-find-file-hook* にフックすると url ファイルが xyzzy で開けなくなるので、*drag-and-drop-hook* にセットするようにしました。

以下の環境で動作確認しました。

『応用例』を追加しました。
url ファイルを D&D すると、カレントバッファのカーソル位置に内容を挿入します。
*insert-url-string-func-alist* は、挿入文字列を生成する関数の連想リストで、要素に関数のペアをセットします。先頭の要素から car 部の関数を実行していき、関数が non-nil を返した要素の cdr 部の関数を使用して挿入文字列を生成します。
*insert-url-string-default-func* には、デフォルトの挿入文字列生成関数をセットします。*insert-url-string-func-alist* で生成関数を決定できなかった場合に使用されます。
以下のものが定義済みです。

html-mode,
//www1.odn.ne.jp/ymtz/html_plus-mode.html">html+-mode, xml-mode のマイナーモード XHTML1.0/1.1:『<a href="URL">ファイル名</a>\n』という文字列を挿入する
lisp-mode, lisp-interaction-mode
『* ファイル名\n <URL>\n』という文字列を挿入する
text-mode
『* ファイル名\n URL\n』という文字列を挿入する
デフォルト
ファイル名\nURL\n』という文字列を挿入する

コード

;; url ファイルが D&D されたら URL を挿入する
; * tips/URLショートカットをD&DされたらURLを挿入する - XyzzyWiki
;   <http://xyzzy.s53.xrea.com/wiki/index.php?cmd=read&page=tips%2FURL%A5%B7%A5%E7%A1%BC%A5%C8%A5%AB%A5%C3%A5%C8%A4%F2D%A1%F5D%A4%B5%A4%EC%A4%BF%A4%E9URL%A4%F2%C1%DE%C6%FE%A4%B9%A4%EB>
; から朴りました。

; url ファイルが D&D されたら URL を挿入するか否か
(defvar *insert-url-string-enable* t)

; デフォルトの挿入文字列生成関数
; 引数  : name - 文字列 - url ファイルのファイル名
; 引数  : url - 文字列 - url ファイルの InternetShortcut セクション->URL キーの値
; 戻り値: 挿入する文字列
(defvar *insert-url-string-default-func*
  #'(lambda (name url)
      (format nil "~A~%~A~%" name url)))

; 関数の連想リスト
; car の関数が non-nil を返すとき cdr の関数で挿入文字列を生成する
; car の関数の引数はドロップされた url ファイルのパス
(defvar *insert-url-string-func-alist*
  (list
   (cons #'(lambda (&optional f)
             (or (member buffer-mode '("html-mode" "html+-mode") :test #'string=)
                 (and (string= buffer-mode "xml-mode")
                      (string-match "^xml:XHTML1\\.\\(?:0-\\(?:Strict\\|Frameset\\|Transitional\\)\\|1\\)$"
                                    mode-name))))
         (let* ((rs #'(lambda (str alst)
                        (dolist (var alst str)
                          (setq str
                                (substitute-string str
                                                   (car var)
                                                   (cdr var))))))
                (getu #'(lambda (url)
                          (funcall rs url '(("&"  . "&amp;")
                                            ("<"  . "&lt;")
                                            (">"  . "&gt;")
                                            ("\"" . "&quot;")))))
                (getn #'(lambda (name)
                          (funcall rs name '(("&" . "&amp;")
                                             ("<" . "&lt;")
                                             (">" . "&gt;"))))))
           #'(lambda (name url)
               (format nil "<a href=\"~A\">~A</a>~%"
                       (funcall getu url)
                       (funcall getn name)))))
   (cons  #'(lambda (&optional f)
              (member buffer-mode '("lisp-mode" "lisp-interaction-mode")
                      :test #'string=))
          #'(lambda (name url)
              (format nil "* ~A~%  <~A>~%" name url)))
   (cons #'(lambda (&optional f)
             (string= buffer-mode "text-mode"))
         #'(lambda (name url)
             (format nil "* ~A~%  ~A~%" name url)))
   ))

; ドロップされた url ファイルをごみ箱へ移動するか否か
(defvar *insert-url-string-kill-shortcut-file* nil)

(defun insert-url-string-get-func (&optional f)
  (or (some #'(lambda (x) (if (funcall (car x) f) (cdr x)))
            *insert-url-string-func-alist*)
      *insert-url-string-default-func*))

;(require "api")
(unless (fboundp 'GetPrivateProfileString)
  (require "wip/winapi")
  (c:define-dll-entry winapi:DWORD GetPrivateProfileString
    (winapi:LPCSTR winapi:LPCSTR winapi:LPCSTR winapi:LPCSTR winapi:DWORD winapi:LPCSTR)
    "kernel32" "GetPrivateProfileStringA"))
(defun insert-url-string (f)
  (let* ((name (pathname-name f))
         (sec (si:make-string-chunk "InternetShortcut"))
         (key (si:make-string-chunk "URL"))
         (non (si:make-string-chunk ""))
         (sz 508)
         (url (si:make-chunk nil sz))
         (file (si:make-string-chunk (map-slash-to-backslash f)))
         (func (insert-url-string-get-func f)))
    ;(win-user::GetPrivateProfileString sec key non url sz file)
    (GetPrivateProfileString sec key non url sz file)
    (setq url (si:unpack-string url 0))
    (insert (funcall func name url))
    (if *insert-url-string-kill-shortcut-file*
        (delete-file f :recycle t))
    (selected-buffer)))

(setq *drag-and-drop-hook*
      #'(lambda (window files)
          (and *insert-url-string-enable*
               (not (minibuffer-window-p window))
               (let ((i 0)
                     (n (list-length files)))
                 (while (< i n)
                   (let ((f (nth i files)))
                     (when (string-equal "url" (pathname-type f))
                       (insert-url-string f)
                       (or (delete f files)
                           (setq files nil)
                           (return))
                       (decf i)
                       (decf n)))
                   (incf i))))
          (if files
              (ed::default-drag-and-drop-hook window files)
            t)))

応用例

以下の関数は、unDonut で現在のタブの URL とタイトルを、カレントバッファのカーソル位置に挿入します。
DonutP.API を使用するので、COM サーバの登録が行われていなければ動作しません。DonutP にもあるメソッドとプロパティしか使用していないので、DonutP でも使えると思います。
引数には、対象のタブの数を指定します。
例えば 3 と指定すると、現在のタブから右方向に 3 個のタブが対象となります。また、負の値を指定すると、左方向になります。
0 を指定すると、すべてのタブが対象となります。
省略時は、1 を指定したときと同じく現在のタブのみが対象となります。
戻り値は、挿入した URL とタイトルの数です。
M-x などでインタラクティブに呼び出すときには、C-u に続けて数字を入力すれば、関数に引数が渡せます。つまり、『C-u 3 M-x insert-url-string-undonut-current-tab』で 『(insert-url-string-undonut-current-tab 3)』を評価したときと同様になります。
インタラクティブに前置引数のみで呼び出したときには、すべてのタブが対象となります。つまり、『C-u M-x insert-url-string-undonut-current-tab』で、『(insert-url-string-undonut-current-tab 0)』を評価したときと同様になります。

(defun insert-url-string-undonut-current-tab (&optional (arg 1))
  (interactive "*p")
  (let ((dntp (ole-create-object "DonutP.API")))
    (if dntp
        (let ((cnt #{dntp.GetTabCount[]}))
          (if (> cnt 0)
              (let* ((all (or (zerop arg)
                              (eq *prefix-args* 'universal-argument)))
                     (idx (if all
                              0
                            #{dntp.TabIndex}))
                     (inc (if (or all
                                  (plusp arg))
                              #'1+
                            #'1-))
                     (ins #'(lambda ()
                              (let ((doc #{dntp.GetDocumentObject[idx]}))
                                (if doc
                                    (let ((url #{doc.URL})
                                          (title #{doc.title}))
                                      (if (string= title "")
                                          (setq title url))
                                      (insert (funcall (insert-url-string-get-func)
                                                       title
                                                       url))
                                      (< -1 (setq idx (funcall inc idx)) cnt)))))))
                (dotimes (i
                          (cond (all (- cnt idx))
                                ((minusp arg) (- arg))
                                (t arg))
                          i)
                  (unless (funcall ins)
                    (return (1+ i))))))))))