all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* bug#424: new feature: killed-buffer
@ 2008-06-16  8:16 pierre kobylanski
  2008-06-17 20:22 ` Stefan Monnier
  2016-02-29  4:56 ` Lars Ingebrigtsen
  0 siblings, 2 replies; 4+ messages in thread
From: pierre kobylanski @ 2008-06-16  8:16 UTC (permalink / raw)
  To: bug-gnu-emacs

[-- Attachment #1: Type: text/plain, Size: 1960 bytes --]

Hi guys,

I'd like to propose a new feature for emacs: the capability to easily
re-open previously killed buffers. Maybe someone else would appreciate
it.


It proposes three user functions:

 * killed-buffers-mode
     Activate/Deactivate remembering killed buffers. To avoid tricky
     cases, only buffer associated with an existing file are taken
     into account.

 * restore-last-killed-buffer
     Restore previously killed buffer. The number of
     killed buffers to remember is configurable.
     Ex: (global-set-key [(meta f8 )] 'restore-last-killed-buffer)

 * mouse-killed-buffers-menu
       Display a menu with last killed buffers. A variable
     controls the number of item displayed, to ensure a
     reasonable size of the popup menu.
       This function respects the `mouse-wheel-follow-mouse'
     setting.
       I attached a screen-shot of such a menu.
     Ex: (global-set-key [C-S-down-mouse-4] 'mouse-killed-buffers-menu)


Compatibilities / Dependencies :
 * require emacs 22.1 or above.
     Indeed, I make use of the `push' macro and the `find-file-hook'
     variables (that obsoletes `find-file-hooks' since v22.1).
 * require file-marker.el
     This is a second file, that defines the file-marker structure.
     Basically, it looks like a marker: it handles a filename and a
     point. But I can't use markers as they end with buffer death.


Maybe you'll be interested in integrating this in next emacs version.
But before that, my code has some bugs to correct:
 - copyright
 - autoload
 - maybe find default keystrokes (that follow emacs spirit).
 - in text-mode, what about `mouse-killed-buffers-menu' and graphical stuff ?
 - when killed-buffer-mode is off, the list of killed buffers is not
    updated, so that we can have a buffer opened that still appear in
    the killed buffers list. Bug or functionality ?


Of course, you have my email for any questions/demands.

Thanks for your involvement and time,
Pierre

[-- Attachment #2: killed-buffers.el --]
[-- Type: application/octet-stream, Size: 13298 bytes --]

;;; killed-buffers.el                                  Pierre Kobylanski
;;;                                                    v1.0.0 2008-06-16


;; DESCRIPTION
;; ===========
;;
;;    Remember last killed buffers
;;    (only those associated with an existing file).
;;
;;
;; DEPENDENCIES
;; ============
;;
;;    * emacs 22.1 or above
;;         >= 22  : push
;;         >= 22.1: find-file-hook
;;
;;    * file-marker.el
;;
;;
;; CONFIGURATION
;; =============
;;    put in your .emacs
;;
;;    ;; make emacs be able to find killed-buffers.el
;;    ;;  (let's assume that direcrory /home/me/emacs.aux/
;;    ;;   contains both files killed-buffers.el and file-marker.el)
;;    (push "/home/me/emacs.aux" load-path)
;;
;;    ;; killed-buffers: remember last killed buffers
;;    (require 'killed-buffers nil t) ; do not fail on error
;;
;;    ;; set up key/mouse bindings
;;    (when (featurep 'killed-buffers)
;;      (global-set-key [(meta f8)]        'restore-last-killed-buffer)
;;      (global-set-key [C-S-down-mouse-4] 'mouse-killed-buffers-menu)
;;    )
;;
;;
;; CUSTOMIZATION
;; =============
;;
;;    M-x customize-group <RET> killed-buffers


(require 'file-marker)


                        ;+------------------+
                        ;|  user functions  |
                        ;+------------------+

(defalias 'restore-last-killed-buffer 'kb-restore-killed-buffer)
(defalias 'mouse-killed-buffers-menu  'kb-mouse-killed-buffers-menu)
(defalias 'killed-buffers-mode        'kb-killed-buffers-mode)



                        ;+-------------------+
                        ;|   group options   |
                        ;|  (for customize)  |
                        ;+-------------------+


(defgroup killed-buffers nil
  "Remember killed buffers."
  :group 'convenience
 ;:prefix "kb-"
  :version "22.1.1")


;; Supporting unlimited undo for killed buffers is nonsense.
(defcustom kb-max-visible-undo 10
  "The maximum number of killed buffers to display in the mouse menu.

0 <= `kb-max-visible-undo'  (0 means zero).

If `kb-max-visible-undo' >= `kb-max-memorized-undo', all killed
buffers will appear in the menu. In the other case, if the number
of killed buffer is higher than `kb-max-visible-undo', only the
last killed buffers will be shown."
  :type 'integer
  :set (lambda (symbol value) (when (>= value 0) (set symbol value)))
  :require 'killed-buffers
  :group 'killed-buffers)


(defcustom kb-max-memorized-undo 25
  "The number of supported undo.

0 <= `kb-max-memorized-undo'  (0 means zero)."
  :type 'integer
  :set (lambda (symbol value) (when (>= value 0) (set symbol value)))
  :require 'killed-buffers
  :group 'killed-buffers)


(defcustom kb-restore-point t
  "When t, also restore the cursor position."
  :type 'boolean
  :require 'killed-buffers
  :group 'killed-buffers)



                      ;+----------------------+
                      ;|  internal variables  |
                      ;+----------------------+


(defvar kb-killed-buffers-list nil
  "The list of killed buffers. Each element is a file-marker.

See function `file-marker'.")



                      ;+-----------------------+
                      ;|  killed-buffers mode  |
                      ;+-----------------------+

(defvar kb-killed-buffers-mode-on t
  "killed-buffers mode activation state.")

(defun kb-killed-buffers-mode ()
  "Activate/Deactivate killed-buffers mode."
  (interactive)
  (setq kb-killed-buffers-mode-on (not kb-killed-buffers-mode-on))
  (message "killed-buffers-mode mode is now %s."
           (if kb-killed-buffers-mode-on "on" "off")))


                             ;+---------+
                             ;|  hooks  |
                             ;+---------+

(defun kb-find-file-hook ()
  "When opening a file, remove it from the `kb-killed-buffers-list'."
  (when kb-killed-buffers-mode-on
    (kb-remove-buffer-from-killed-list buffer-file-name)))

(defun kb-kill-buffer-hook ()
  "When killing a buffer, save it into the `kb-killed-buffers-list'."
  (when kb-killed-buffers-mode-on
    (kb-add-buffer-to-killed-list (current-buffer))))


(add-hook 'find-file-hook   'kb-find-file-hook)
(add-hook 'kill-buffer-hook 'kb-kill-buffer-hook)



                    ;+---------------------------+
                    ;|  finding a killed buffer  |
                    ;+---------------------------+


(defun kb-find-killed-buffer-index (killed-buffer)
  "Find the index of a killed buffer in `kb-killed-buffers-list'.

If nil or not found, return nil.

Possible value/type for KILLED-BUFFER are:
     nil             -->  not set (in this case, return nil)
    `stringp'        -->  the killed buffer name
    `bufferp'        -->  an existing buffer
    `file-marker-p'  -->  a killed buffer
     <other>         -->  signal an error"
  (let ((killed-buffer-filename
         (cond ((null    killed-buffer)  nil)
         ((file-marker-p killed-buffer) (file-marker-filename killed-buffer))
               ((bufferp killed-buffer) (kb-aux-buffer-filename-or-name killed-buffer))
               ((stringp killed-buffer)  killed-buffer)
               ( t (error "\n\nError in killed-buffer.el: (kb-find-killed-buffer-index killed-buffer)\n  KILLED-BUFFER type is \"%s\",\n  but acceptable value/types are: nil, string, buffer, and file-marker\n" (prin1-to-string (type-of killed-buffer)))))))

    (kb-aux-list-index-find
     (lambda (killed-buffer)
       (string-equal killed-buffer-filename
                     (file-marker-filename killed-buffer)))
     kb-killed-buffers-list)))



                    ;+--------------------------+
                    ;|  adding a killed-buffer  |
                    ;+--------------------------+



(defun kb-is-buffer-eligible-for-killed-list (buffer)
  "Return t if BUFFER is eligible to be put in `kb-killed-buffers-list'.

BUFFER is eligible if it verifies the following conditions:
 - BUFFER is an existing buffer
 - BUFFER is associated with an existing file"
  (and  buffer
        (bufferp buffer)
        (let ((bfn (buffer-file-name buffer)))
          (if bfn (file-exists-p bfn) nil))))



(defun kb-add-buffer-to-killed-list (buffer)
  "If BUFFER is eligible, save it into the `kb-killed-buffers-list'."
  (when (and ; BUFFER is eligible and not already in kb-killed-buffers-list
         (kb-is-buffer-eligible-for-killed-list buffer)
         (not (kb-find-killed-buffer-index buffer)))
    (let ((fn (kb-aux-buffer-filename-or-name buffer))
          (pt (save-excursion (set-buffer buffer) (point))))
      ;; don't let the list grow indefinitely
      (kb-aux-list-ntrunc-inplace
       (1- kb-max-memorized-undo) kb-killed-buffers-list)
      ;; put the killed buffer into the list
      (push (file-marker fn pt) kb-killed-buffers-list))))



                   ;+----------------------------+
                   ;|  removing a killed-buffer  |
                   ;+----------------------------+


(defun kb-remove-buffer-from-killed-list (killed-buffer)
  "Remove a killed buffer from `kb-killed-buffers-list',
and return the corresponding file-marker.

See `kb-find-killed-buffer-index' for accepted values of KILLED-BUFFER."
  (let ((n (kb-find-killed-buffer-index killed-buffer)))
    (when n (prog1
                (nth n kb-killed-buffers-list)
              (setq kb-killed-buffers-list
                    (kb-aux-list-nth-remove n kb-killed-buffers-list))))))



                   ;+-----------------------------+
                   ;|  restoring a killed-buffer  |
                   ;+-----------------------------+


(defun kb-restore-killed-buffer (&optional killed-buffer)
  "Restore a killed buffer.

If KILLED-BUFFER is nil, restore the last killed one."
  (interactive)

  ;; restore last one if killed-buffer not specified
  (when (null killed-buffer)
    (if kb-killed-buffers-list
        (setq killed-buffer (nth 0 kb-killed-buffers-list))
      (message "%s" "no killed buffers")))

  (when killed-buffer
    (let ((buf-filename (file-marker-filename killed-buffer))
          (buf-point    (file-marker-point    killed-buffer)))
      (find-file buf-filename)
      (kb-remove-buffer-from-killed-list buf-filename)
      (when (and buf-point kb-restore-point) (goto-char buf-point)))))



                          ;+--------------+
                          ;|  mouse menu  |
                          ;+--------------+


(defun kb-mouse-killed-buffers-menu (event)
  "Display a popup menu with last killed buffers. Re-open the selected one."
  (interactive "e")
  (if kb-killed-buffers-list
     (progn
       (mouse-minibuffer-check event)
       (let* ((buf-name-max-len 0)
              (names (mapcar (lambda (buf)
               (let* ((buf-filename (file-marker-filename buf))
                      (buf-name     (file-name-nondirectory buf-filename))
                      (buf-dir      (file-name-directory buf-filename)))
                 (let ((home-dir (getenv-internal "HOME")))
                   (if (string-match (concat "^\\(" home-dir "\\)/") buf-dir)
                       (setq buf-dir
                             (concat "~" (substring buf-dir (match-end 1))))))
                (setq buf-name-max-len (max buf-name-max-len (length buf-name)))
                `(,buf-name ,buf-dir ,buf-filename)))
                kb-killed-buffers-list))
              (buf-name-max-len (min 25 buf-name-max-len))
              (buf-dir-max-len  35)
              (strs (mapcar (lambda (x)
               (let* ((buf-name     (car  x))
                      (buf-dir      (cadr x))
                      (buf-name-len (length buf-name)))
                 (if (> buf-name-len buf-name-max-len)
                     (kb-aux-string-see-end (car (cddr x))
                                         (+ buf-name-max-len 3 buf-dir-max-len))
                   (concat
                    buf-name
                    (make-string (- buf-name-max-len buf-name-len) ? )
                    "   "
                    (kb-aux-string-see-end buf-dir buf-dir-max-len))))) names))
              (strs (kb-aux-list-ntrunc-inplace kb-max-visible-undo strs))
              (menu (kb-aux-simple-1level-x-popup-menu "killed buffers" strs)))
         (let ((n (x-popup-menu event menu)))
           (when n
             (with-selected-window ; respect mouse-wheel-follow-mouse settings
                 (if (and (boundp 'mouse-wheel-follow-mouse)
                          mouse-wheel-follow-mouse)
                     (mwheel-event-window event) (selected-window))
               (kb-restore-killed-buffer (nth n kb-killed-buffers-list)))))))
    (message "%s" "no killed buffers")))



                      ;+-----------------------+
                      ;|  auxiliary functions  |
                      ;+-----------------------+


(defun kb-aux-string-see-end (str &optional maxlen)
  "`killed-buffers-mode' auxillary function.

Cut the beginning of the string STR if too long.
If MAXLEN is nil, use the current frame width."
  (let ((maxlen (cond ((or (null maxlen) (< maxlen 0)) (frame-width))
                      ( t                              maxlen)))
        (len (length str)))
    (if (and (> len maxlen) (> maxlen 3))
        (concat "..." (substring str (- len maxlen -3)))
      str)))


(defun kb-aux-list-index-find (p l)
  "`killed-buffers-mode' auxiliary function.

Find the index of the first element of list L that satisfies predicate P."
  (let ((len (length l)) (i 0) (ll l))
    (while (and (< i len) (not (funcall p (pop ll)))) (setq i (1+ i)))
    (if (< i len) i nil)))


(defun kb-aux-list-ntrunc-inplace (n l)
  "`killed-buffers-mode' auxiliary function.

Trunc list L to at most N elements (modify the list)."
  (let ((len (length l)))
    (if (and (> n 0) (< n len)) (setcdr (nthcdr (1- n) l) nil)))
  l)


(defun kb-aux-list-nth-remove (n l)
  "`killed-buffers-mode' auxiliary function.

Remove the Nth element of list L (do *not* modify the list)."
  (let ((len (length l)))
    (append (butlast l (- len n)) (last l (- len (1+ n))))))


(defun kb-aux-is-special-buffer (buffer)
  "Return t if buffer is a special buffer."
  (and
   (null (buffer-file-name buffer))
   (kb-aux-is-special-buffer-name (buffer-name buffer))))


(defun kb-aux-is-special-buffer-name (buffer-name)
  "Return t if buffer-name is standard name for a special buffer.

Special buffers names usually start with a space (' ') or a star ('*')."
  (let ((c (elt buffer-name 0)))
    (or (= c 32) (= c 42)))) ; SPC or '*'


(defun kb-aux-buffer-filename-or-name (buffer)
  "Return buffer-file-name if non nil, buffer-name otherwise."
  (let ((bfn (buffer-file-name buffer)))
    (if bfn bfn (buffer-name buffer))))


(defun kb-aux-simple-1level-x-popup-menu (title l)
  "`killed-buffers-mode' auxiliary function.

Create a one level popup menu, to use in conjunction with `x-popup-menu'.
It returns
  - the rank of the item you've selected (beginning at 0),
  - or nil if the menu is empty or if nothing has been selected.

TITLE: menu title
L    : items list"
  (let* ((menu nil) (rl (reverse l)) (len (length rl)) (i len))
    (while (>= (setq i (1- i)) 0)
      (setq menu (cons (cons (pop rl) i) menu)))
    (setq menu (cons "dummy" menu))
    (setq menu (cons title (list menu)))
    menu))



(provide 'killed-buffers)

;;; killed-buffers.el ends here

[-- Attachment #3: file-marker.el --]
[-- Type: application/octet-stream, Size: 2589 bytes --]

;;; file-marker.el                                     Pierre Kobylanski
;;;                                                    v1.0.0 2008-06-15

;; DESCRIPTION
;; ===========
;;
;;    A file-marker is a data structure to remember
;;    a filename and a point (the cursor position in
;;    that file).
;;
;;    Unlike a marker, it survives to a buffer death.



(defun file-marker-p (obj)
  "Return t if OBJ is a file-marker."
  (and                                      ; A file-marker is
   (vectorp obj)                            ; a vector `v'
   (= 3    (length obj))                    ;  - of length 3
   (eq 'file-marker (elt obj 0))            ;  - with v[0] = 'file-marker
   (let ((fn (elt obj 1)) (pt (elt obj 2))) ;  - and v[1] the filename, and v[2]
     (and (stringp fn) (or (null pt) (integerp pt))))))    ; the cursor position



(defun check-file-marker-p (obj)
  "Signal an error if OBJ is not a file-marker."
  (when (not (file-marker-p obj))
    (error "Not a file-marker (is %s)" (prin1-to-string (type-of obj)))))



(defun file-marker (&optional obj point)
  "Construct a file-marker.

OBJ is either a buffer, a marker, a string, or nil (means current buffer).
  It is then transformed into a string.

POINT is an integer or nil."
  (let* (
         (buffer (cond
           ((bufferp obj)  obj)
           ((markerp obj)  (let ((buf (marker-buffer obj)))
                            (if buf buf
                        (error "Marker doesn't refer to an existing buffer")) ))
           ((stringp obj)  nil) ; very special value
           ((null    obj)  (current-buffer))
           ( t         (error "Bad type %s" (prin1-to-string (type-of obj)))) ))

         (filename (if (null buffer) ; very special value => (stringp obj)
                       obj
                     (let ((fn (buffer-file-name buffer)))
                       (if fn fn (buffer-name) ))))

         (point (cond (point  point)
                      (buffer (save-excursion (set-buffer buffer) (point)))
                      (t      nil)))
        )
    `[file-marker ,filename ,point]))



(defun file-marker-filename (file-marker)
  "Return the filename of the file-marker.

FILE-MARKER must be a file-marker. An error is signaled otherwise."
  (check-file-marker-p file-marker)
  (elt file-marker 1))


(defun file-marker-point (file-marker)
  "Return the point of the file-marker. Can be nil.

FILE-MARKER must be a file-marker. An error is signaled otherwise."
  (check-file-marker-p file-marker)
  (elt file-marker 2))



(provide 'file-marker)

;;; file-marker.el ends here

[-- Attachment #4: killed-buffers.png --]
[-- Type: image/png, Size: 4138 bytes --]

^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#424: new feature: killed-buffer
  2008-06-16  8:16 bug#424: new feature: killed-buffer pierre kobylanski
@ 2008-06-17 20:22 ` Stefan Monnier
  2017-03-14  4:50   ` npostavs
  2016-02-29  4:56 ` Lars Ingebrigtsen
  1 sibling, 1 reply; 4+ messages in thread
From: Stefan Monnier @ 2008-06-17 20:22 UTC (permalink / raw)
  To: pierre kobylanski; +Cc: 424, bug-gnu-emacs

> I'd like to propose a new feature for emacs: the capability to easily
> re-open previously killed buffers. Maybe someone else would appreciate it.

Could this be integrated with recentf, maybe?

>  * require file-marker.el
>      This is a second file, that defines the file-marker structure.
>      Basically, it looks like a marker: it handles a filename and a
>      point. But I can't use markers as they end with buffer death.

Why not use a bookmark?


        Stefan






^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#424: new feature: killed-buffer
  2008-06-16  8:16 bug#424: new feature: killed-buffer pierre kobylanski
  2008-06-17 20:22 ` Stefan Monnier
@ 2016-02-29  4:56 ` Lars Ingebrigtsen
  1 sibling, 0 replies; 4+ messages in thread
From: Lars Ingebrigtsen @ 2016-02-29  4:56 UTC (permalink / raw)
  To: pierre kobylanski; +Cc: 424

"pierre kobylanski" <pierre.kobylanski@gmail.com> writes:

> I'd like to propose a new feature for emacs: the capability to easily
> re-open previously killed buffers. Maybe someone else would appreciate
> it.
>
> It proposes three user functions:
>
>  * killed-buffers-mode
>      Activate/Deactivate remembering killed buffers. To avoid tricky
>      cases, only buffer associated with an existing file are taken
>      into account.
>
>  * restore-last-killed-buffer
>      Restore previously killed buffer. The number of
>      killed buffers to remember is configurable.
>      Ex: (global-set-key [(meta f8 )] 'restore-last-killed-buffer)

[...]

>  * require file-marker.el
>      This is a second file, that defines the file-marker structure.
>      Basically, it looks like a marker: it handles a filename and a
>      point. But I can't use markers as they end with buffer death.

This is perhaps covered by the desktop package (along with recentf) now?
I mean, especially the file marker stuff.  But perhaps desktop doesn't
keep track of killed buffers?

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no





^ permalink raw reply	[flat|nested] 4+ messages in thread

* bug#424: new feature: killed-buffer
  2008-06-17 20:22 ` Stefan Monnier
@ 2017-03-14  4:50   ` npostavs
  0 siblings, 0 replies; 4+ messages in thread
From: npostavs @ 2017-03-14  4:50 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: pierre.kobylanski, 424

tags 424 wontfix
close 424
quit

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>>  * require file-marker.el
>>      This is a second file, that defines the file-marker structure.
>>      Basically, it looks like a marker: it handles a filename and a
>>      point. But I can't use markers as they end with buffer death.
>
> Why not use a bookmark?

bojohan+news@dd.chalmers.se (Johan "Bockgård)" writes:

> Stefan Monnier <monnier@iro.umontreal.ca> writes:
>
>>> I'd like to propose a new feature for emacs: the capability to easily
>>> re-open previously killed buffers. Maybe someone else would appreciate it.
>>
>> Could this be integrated with recentf, maybe?
>
> Cf. iswitchb-use-virtual-buffers.

Lars Ingebrigtsen <larsi@gnus.org> writes:

> "pierre kobylanski" <pierre.kobylanski@gmail.com> writes:
>
>>  * require file-marker.el
>>      This is a second file, that defines the file-marker structure.
>>      Basically, it looks like a marker: it handles a filename and a
>>      point. But I can't use markers as they end with buffer death.
>
> This is perhaps covered by the desktop package (along with recentf) now?
> I mean, especially the file marker stuff.  But perhaps desktop doesn't
> keep track of killed buffers?

Since the OP never responded to any of the comments on their patch, I'm
closing this.





^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2017-03-14  4:50 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-06-16  8:16 bug#424: new feature: killed-buffer pierre kobylanski
2008-06-17 20:22 ` Stefan Monnier
2017-03-14  4:50   ` npostavs
2016-02-29  4:56 ` Lars Ingebrigtsen

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.