From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Kelly Dean Newsgroups: gmane.emacs.devel Subject: Proposal for a closed-buffer tracker Date: Sun, 22 Feb 2015 04:11:54 +0000 Message-ID: References: NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Trace: ger.gmane.org 1424578421 9138 80.91.229.3 (22 Feb 2015 04:13:41 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Sun, 22 Feb 2015 04:13:41 +0000 (UTC) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Sun Feb 22 05:13:30 2015 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1YPNv6-0000ae-UU for ged-emacs-devel@m.gmane.org; Sun, 22 Feb 2015 05:13:29 +0100 Original-Received: from localhost ([::1]:38875 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YPNv6-0002vZ-6S for ged-emacs-devel@m.gmane.org; Sat, 21 Feb 2015 23:13:28 -0500 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:40701) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YPNv1-0002ub-AI for emacs-devel@gnu.org; Sat, 21 Feb 2015 23:13:25 -0500 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1YPNuy-00081r-0g for emacs-devel@gnu.org; Sat, 21 Feb 2015 23:13:23 -0500 Original-Received: from relay4-d.mail.gandi.net ([2001:4b98:c:538::196]:47148) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1YPNux-00081h-O1 for emacs-devel@gnu.org; Sat, 21 Feb 2015 23:13:19 -0500 Original-Received: from mfilter6-d.gandi.net (mfilter6-d.gandi.net [217.70.178.135]) by relay4-d.mail.gandi.net (Postfix) with ESMTP id CEDAA172085 for ; Sun, 22 Feb 2015 05:13:18 +0100 (CET) X-Virus-Scanned: Debian amavisd-new at mfilter6-d.gandi.net Original-Received: from relay4-d.mail.gandi.net ([217.70.183.196]) by mfilter6-d.gandi.net (mfilter6-d.gandi.net [10.0.15.180]) (amavisd-new, port 10024) with ESMTP id rW+0uKkdO1rB for ; Sun, 22 Feb 2015 05:13:17 +0100 (CET) X-Originating-IP: 66.220.3.179 Original-Received: from localhost (gm179.geneticmail.com [66.220.3.179]) (Authenticated sender: kelly@prtime.org) by relay4-d.mail.gandi.net (Postfix) with ESMTPSA id EE2C9172070 for ; Sun, 22 Feb 2015 05:13:13 +0100 (CET) X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2001:4b98:c:538::196 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:183378 Archived-At: Below is code for a closed-buffer tracker. It lets you reopen closed buff= ers, and restores the major mode, minor modes, point, mark, mark ring, an= d other buffer-local variables returned by the function desktop-buffer-in= fo. Currently, it's implemented only for file-visiting buffers. It's comp= arable to the =C2=ABclosed tabs=C2=BB feature of modern web browsers, and= useful for the same reasons. Note that although it uses functions in desktop.el, it doesn't require de= sktop-save-mode to be enabled; the two operate independently. Also note, = it relies on a recent patch to trunk; it won't work on 24.4 or 24.5. And = the =C2=ABsilently=C2=BB macro is generated from second-level just becaus= e I happen to use the generator for other things not included here. Stefan suggested I submit this feature as a patch. If other people might = find it useful, should it go into desktop.el? Or perhaps GNU Elpa? Curren= tly it's just part of a larger convenience package at: http://prtime.org/emacs/usablizer.html The =C2=ABsilently=C2=BB macro and its generator are in another package (= that Usablizer requires) at: http://prtime.org/emacs/vimizer.html ;;; Utilities (defmacro dlet (binders &rest body) "Like `let', but always bind dynamically, even if `lexical-binding' is = t. Uses the local-specialness feature of `defvar'." (unless (listp binders) (error "%S is not a list" binders)) ;; Contain the local-specialness, so it doesn't infect =C2=ABlet=C2=BBs= outside dlet, ;; because the purpose of local-specialness is to avoid global infectio= n. `(progn ,@(let (vardefs) ; Generate all the =C2=ABdefvar=C2=BBs (dolist (binder binders (nreverse vardefs)) (cond ((symbolp binder) (push `(defvar ,binder) vardefs)) ((and (listp binder) (symbolp (car binder))) (push `(defvar ,(car binder)) vardefs)) (t (error "%S is not a symbol or list" binder))))) (let ,binders ,@body))) (defmacro define-function-suppressor (wrapper victim docstring) "Make a macro named WRAPPER (a symbol), with DOCSTRING, that takes a bo= dy and evaluates it with function VICTIM suppressed." `(defmacro ,wrapper (&rest body) ,docstring `(cl-letf (((symbol-function ',',victim) (lambda (&rest _dummy) ())= )) ,@body))) (define-function-suppressor silently message "Do BODY without messaging anything.") ;; Copied from assq-delete-all in subr.el, but =C2=ABeq=C2=BB replaced by= =C2=ABequal=C2=BB (defun assoc-delete-all (key alist) "Delete from ALIST all elements whose car is `equal' to KEY. Return the modified alist. Elements of ALIST that are not conses are ignored." (while (and (consp (car alist)) (equal (car (car alist)) key)) (setq alist (cdr alist))) (let ((tail alist) tail-cdr) (while (setq tail-cdr (cdr tail)) (if (and (consp (car tail-cdr)) (equal (car (car tail-cdr)) key)) (setcdr tail (cdr tail-cdr)) (setq tail tail-cdr)))) alist) ;;; Closed-buffer tracker. Inspired by: ;;; http://stackoverflow.com/questions/2227401/how-to-get-a-list-of-last-= closed-files-in-emacs (defvar closed-buffer-history nil "Reverse chronological list of closed buffers. This list stores filenames and/or full buffer states as stored by `desktop-save-mode', including point, mark, and various other buffer-loca= l variables. The list size is limited by `closed-buffer-history-max-saved-items' and `closed-buffer-history-max-full-items'. When a buffer already in the list is closed again, it's moved to the head= of the list.") (defvar closed-buffer-history-max-saved-items 1000 "Max items to save on `closed-buffer-history' list. Use -1 for unlimited, or zero to disable tracking closed files. If disabled after having been enabled, `closed-buffer-history' will retai= n the list from when it was enabled, even though no new items will be added= to it. To clear the list, set it to nil. See also `closed-buffer-history-max-full-items'.") (defvar closed-buffer-history-max-full-items 100 "Max full items to save on `closed-buffer-history' list. Use -1 for unlimited, or zero to disable tracking of full items. If this limit is less than `closed-buffer-history-max-saved-items', then non-full items will be stored for the difference. If this limit is greater, then `closed-buffer-history-max-saved-items' is the controlling limit. When ne= w items are added to `closed-buffer-history', full items which exceed this limit are converted to non-full items. The purpose of that is to save spa= ce. A full item is a buffer state, including `buffer-file-name', `point', `mark', `mark-ring', `major-mode', minor modes, and various other buffer-local variables as configured for `desktop-save-mode', but excludi= ng the buffer contents, which are stored only in the named file. A non-full item is just a file name.") (defun untrack-closed-buffer (name) ;; Could be just name, or info list; delete in either case (setq closed-buffer-history (delete name (assoc-delete-all name closed-buffer-history)))) (defun track-closed-buffer () (when (and buffer-file-name (not (=3D closed-buffer-history-max-saved-i= tems 0))) ;; Remove from not-head of list (untrack-closed-buffer buffer-file-name) ;; Add to head of list (pushnew (if (desktop-save-buffer-p buffer-file-name (buffer-name) ma= jor-mode) (cdr (save-current-buffer (desktop-buffer-info (current-buffer)))) buffer-file-name) closed-buffer-history) ;; Truncate excess elements (let* ((max-full closed-buffer-history-max-full-items) (max-saved closed-buffer-history-max-saved-items) (truncatees (nthcdr max-saved closed-buffer-history)) demotees) (and (> max-saved 0) truncatees (setcdr truncatees nil)) (unless (< max-full 0) (setq demotees (nthcdr max-full closed-buffer-history)) ;; Demote buffer info lists to filenames. (letrec ((demote (lambda (x) (when (and (consp x) (consp (car x))= ) (setcar x (caar x)) (funcall demot= e (cdr x)))))) (funcall demote demotees)))))) (defun reopen-buffer (name &optional remove-missing select) "Open file, and restore buffer state if recorded in `closed-buffer-hist= ory'. Return buffer for the opened file, or nil if not listed in `closed-buffer= -history'. If unable to open file, then remove from `closed-buffer-history' if confi= rmed interactively or REMOVE-MISSING is non-nil, or signal error if it is nil and reopen-buffer was not called interactively. If called interactively, or SELECT is non-nil, then switch to the buffer.= " (interactive (list (ido-completing-read "Last closed: " (mapcar (lambda (x) (if (consp x) (car x) x= )) closed-buffer-history) nil t) nil t)) (let* ((bufinfo (assoc name closed-buffer-history)) (bufinfo (or bufinfo (if (memq name closed-buffer-history) (make-list 8 nil))))) (assert bufinfo) ;;Load from info list, using base filename as new buffer name. (let ((buf ;; Set variables needed by desktop-create-buffer. ;; Need dlet because they're not globally special, but only lo= cally ;; special in desktop.el, which according to Stefan, is not we= ird. (dlet ((desktop-buffer-ok-count 0) (desktop-buffer-fail-count 0) desktop-first-buffer) (silently ; Silence desktop-restore-file-buffer if file = can't be found (apply 'desktop-create-buffer (string-to-number desktop= -file-version) name (file-name-nondirectory name) (cddr bufinfo= )))))) (if buf (progn (untrack-closed-buffer name) (with-current-buffer buf (run-hooks 'desktop-delay-hook)) (setq desktop-delay-hook nil) (when select ;; 3 lines copied from desktop-restore-file-buffer in d= esktop.el (condition-case nil (switch-to-buffer buf) (error (pop-to-buffer buf)))) buf) (when (or remove-missing (and (called-interactively-p 'any) (y-or-n-p (format "Failed to open file %s; remove from closed= -buffer-history? " name)))) (untrack-closed-buffer name)) (unless (or remove-missing (called-interactively-p 'any)) (error "Failed to open file %s" name)))))) (add-hook 'kill-buffer-hook #'track-closed-buffer) (global-set-key [S-XF86Close] #'reopen-buffer)