最近閉じたファイル

silog - memo/xyzzy/file #最後に閉じたファイルを開くインスパイヤされました。
今回の改造にあたって加えられた新たなオリジナリティは以下。

  • add-history でヒストリに登録するようにしました。
  • ダイアログを追加しました。

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

recent-closed-file-open-last-one で最後に閉じたファイルを開きます。
recent-closed-file-open-dialog で最近閉じたファイルをダイアログ表示します。ファイルを選択して『OK』を押すと、選択したファイルを開きます。
開いたファイルはヒストリから消されます。
ヒストリの最大個数は *minibuffer-maximum-history-count* です。

コード

;; 最近閉じたファイル
; * silog - memo/xyzzy/file #最後に閉じたファイルを開く
;   <http://white.s151.xrea.com/wiki/index.php?memo%2Fxyzzy%2Ffile#k96261fd>
; から朴りました。
(defvar *recent-closed-file-history* nil)

; ヒストリ変数をファイルに保存する
; 削除するときは
; > (unregister-history-variable '*recent-closed-file-history*)
; するのを忘れるな
;(define-history-variable *recent-closed-file-history* nil)

(defun recent-closed-file-add-history ()
  (let ((file (get-buffer-file-name)))
    (if file
        (if (file-exist-p file)
            (add-history file '*recent-closed-file-history*)
          (or (delete file *recent-closed-file-history* :test #'path-equal)
              (setq *recent-closed-file-history* nil)))))
  t)
#|
; 保存するヒストリの個数を別に設定する
; 負の値を指定すると無制限
(defvar *recent-closed-file-maximum-history-count*
  *minibuffer-maximum-history-count*)

(defun recent-closed-file-add-history ()
  (let ((file (get-buffer-file-name)))
    (when file
      (or (delete file *recent-closed-file-history* :test #'path-equal)
          (setq *recent-closed-file-history* nil))
      (if (file-exist-p file)
          (push file *recent-closed-file-history*))))
  (let (n)
    (and (<= 0 *recent-closed-file-maximum-history-count*)
         (setq n (- (list-length *recent-closed-file-history*)
                    *recent-closed-file-maximum-history-count*))
         (> n 0)
         (or (nbutlast *recent-closed-file-history* n)
             (setq *recent-closed-file-history* nil))))
  t)
|#
(add-hook '*query-kill-buffer-hook* 'recent-closed-file-add-history)

; 最後に閉じたファイルを開く
(defun recent-closed-file-open-last-one ()
  (interactive)
  (unless *recent-closed-file-history*
    (plain-error "ヒストリは空です"))
  (let ((file (pop *recent-closed-file-history*)))
    (unless (file-exist-p file)
      (plain-error "~A: そんなファイルはありません" file))
    (find-file file)))

; ダイアログ
(defvar *recent-closed-file-open-dialog-template*
  `(dialog 0 0 298 117
           (:caption "最近閉じたファイル")
           (:font 9 "MS UI Gothic")
           (:control
            (:listbox IDC_LIST nil #x50b10119 5 6 288 84)
            (:button IDOK "&OK" #x50010001 6 96 50 14)
            (:button IDCANCEL "&Cancel" #x50010000 60 96 50 14))))

(defun recent-closed-file-open-dialog ()
  (interactive)
  (let ((hlist (mapcar #'(lambda (x)
                           (list (abbreviate-display-string x 96 t) x))
                       *recent-closed-file-history*)))
    (multiple-value-bind (result data)
        (let ((omode (get-ime-mode)))
          (toggle-ime nil)
          (unwind-protect
              (dialog-box *recent-closed-file-open-dialog-template*
                          `((IDC_LIST . ,hlist))
                          `((IDC_LIST :column (1)
                                      :must-match t
                                      :enable (IDOK))))
            (toggle-ime omode)))
      (if result
          (let ((lst (cdr (assoc 'IDC_LIST data)))
                dlst)
            (dolist (var lst t)
              (let ((filename (nth 1 var)))
                (or (delete filename *recent-closed-file-history*)
                    (setq *recent-closed-file-history* nil))
                (if (file-exist-p filename)
                    (progn
                      (find-file filename)
                      (add-history filename '*minibuffer-file-name-history*))
                  (push filename dlst))))
            (if dlst
                (let ((lstlen (list-length lst))
                      (dlstlen (list-length dlst)))
                  (if (= lstlen dlstlen 1)
                      (message "~A: そんなファイルはありません" (car dlst))
                    (msgbox (format nil
                                    "~D 個中 ~D 個のファイルが見つかりませんでした~%~{~%~A~}"
                                    lstlen dlstlen dlst)))
                  nil)
              t))))))

; 『ファイル』メニューに追加
(add-hook '*post-startup-hook*
          #'(lambda ()
              (let* ((file (get-menu *app-menu* 'ed::file))
                     (pos (get-menu-position file :above-kill-xyzzy)))
                (insert-menu-item file pos nil
                                  "最近閉じたファイル(&L)..."
                                  'recent-closed-file-open-dialog
                                  #'(lambda ()
                                      (or *recent-closed-file-history*
                                          :disable))))))

最近使ったファイル

xyzzy の音 - ファイル操作 #最近使ったファイル一覧インスパイヤされました。
今回の改造にあたって加えられた新たなオリジナリティは以下。

  • ダイアログで複数選択できるようにしました。
  • 存在しないファイルを開こうとした時に、ヒストリから削除するようにしました。

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

最近使ったファイルをダイアログ表示します。
ダイアログでファイルを選択して『OK』を押すと、選択したファイルを開きます。

コード

;; 最近使ったファイル
; * xyzzy の音 - ファイル操作 #最近使ったファイル一覧
;   <http://hie.s64.xrea.com/xyzzy/note/file-handling.html#list-recents>
; から朴りました。

(defvar *recent-file-open-dialog-template*
  `(dialog 0 0 298 117
           (:caption "最近使ったファイル")
           (:font 9 "MS UI Gothic")
           (:control
            (:listbox IDC_LIST nil #x50b10119 5 6 288 84)
            (:button IDOK "&OK" #x50010001 6 96 50 14)
            (:button IDCANCEL "&Cancel" #x50010000 60 96 50 14))))

(defun recent-file-open-dialog ()
  (interactive)
  (let ((hlist (mapcar #'(lambda (x)
                           (list (abbreviate-display-string x 96 t) x))
                       *minibuffer-file-name-history*)))
    (multiple-value-bind (result data)
        (let ((omode (get-ime-mode)))
          (toggle-ime nil)
          (unwind-protect
              (dialog-box *recent-file-open-dialog-template*
                          (list (cons 'IDC_LIST hlist))
                          '(list (IDC_LIST :column (1)
                                           :must-match t
                                           :enable (IDOK))))
            (toggle-ime omode)))
      (if result
          (let ((lst (cdr (assoc 'IDC_LIST data)))
                dlst)
            (dolist (var lst t)
              (let ((filename (nth 1 var)))
                (cond ((file-exist-p filename)
                       (find-file filename)
                       (add-history filename '*minibuffer-file-name-history*))
                      (t
                       (push filename dlst)
                       (or (delete filename *minibuffer-file-name-history*)
                           (setq *minibuffer-file-name-history* nil))))))
            (if dlst
                (let ((lstlen (list-length lst))
                      (dlstlen (list-length dlst)))
                  (if (= lstlen dlstlen 1)
                      (message "~A: そんなファイルはありません" (car dlst))
                    (msgbox (format nil
                                    "~D 個中 ~D 個のファイルが見つかりませんでした~%~{~%~A~}"
                                    lstlen dlstlen dlst)))
                  nil)
              t))))))

; メニューの『ファイル』->『最近使ったファイル...』と入れ替える
(add-hook '*post-startup-hook*
          #'(lambda ()
              (let* ((file (get-menu *app-menu* 'ed::file))
                     (pos (1+ (get-menu-position file :above-recent))))
                (delete-menu file pos t)
                (insert-menu-item file pos nil
                                  "最近使ったファイル(&F)..."
                                  'recent-file-open-dialog
                                  #'(lambda ()
                                      (or *minibuffer-file-name-history*
                                          :disable))))))

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))))))))))

anonymous苦労者へようこそ!

このページは pipehead 専用の日記 (チラシの裏) です。*1

さっそく「日記を書く」をクリックして最初の記事を書いてみました。

日々の出来事やテレビ番組の感想、普段考えていることなどは書きません。

このページはテキトーにメンテナンスされているので、不正確・不完全な可能性があります。

*1:この文章はイントロダクションです。

はてなダイアリーへようこそ!

このページはあなた専用の日記(ブログ)です。*1

さっそく「日記を書く」をクリックして最初の記事を書いてみましょう。

はてなダイアリーの一番簡単な使い方を知りたい方は、以下の動画をご覧ください。

(再生ボタンをクリックすると、はてなダイアリーの使い方を音声と動画で見ることができます。)

はてなダイアリーのヘルプでは、このような動画を交えた使い方の説明や、文字の色の付け方、本やDVDを紹介する「はてな記法」の使い方を解説しています。

より詳しいはてなダイアリーの使い方を知りたい方は、以下のヘルプをご覧ください。


それでは、日々の出来事やテレビ番組の感想、普段考えていることなど、あなたならではの日記を書いて楽しんでください!

*1:この文章はサンプルです。実際に自分の記事を書くときには削除しても大丈夫です。