Subject: Re: bug#15189: 24.3.50; display-buffer does not work well with custom frames.

From: Keith David Bershatsky <[email-masked]>
Date: Wed, 28 Aug 2013 17:21:52 -0700

Okay, I think I have a better understanding now of how display-buffer works in conjunction with the display-buffer-alist.  There just weren't any really good examples on the internet or in the official documentation from which to work from.  I've updated my post on stackoverflow.com, and that should help other novice programmers (like myself) in the future who Google some of the relevant keywords.   I noticed quite a few comments on the internet about the display-buffer family of functions being worthy of a doctoral thesis due to the level of complexity.  And with my legal background, I was beginning to think that this project was similar to understanding the rule against perpetuities -- something that all law school students fear will be tested:  http://en.wikipedia.org/wiki/Rule_against_perpetuities

I recommend that the documentation be updated to include several working examples of the display-buffer family of functions, which would help to remove the mysticism surrounding the usage.  The level of "perceived" complexity would be significantly reduced once those working examples become available.

Thank you so much for teaching me a few new things about programming and the display-buffer family.

In case you or anyone else who has been reading these e-mail exchanges is interested in the project I've been working on, I am resurrecting Frame-Bufs written by Al Parker a few years ago and marrying it with Tabbar.  It's far from done, but it is working already and is coming along quite nicely thanks to all the wonderful help from people like you :)

https://github.com/lawlist/tabbar-lawlist

Thanks again,

Keith

;;;;;;;;;;;;;;;;; DISPLAY-BUFFER-ALIST and DISPLAY-BUFFER ;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun example ()
  (interactive)
  ;; condition # 3 | file-visiting buffer
  (lawlist-find-file "*bar*")
  (set-frame-height (selected-frame) 20)
  (set-frame-width (selected-frame) 80)
  (set-frame-position (selected-frame) 0 0)
  (message "\*bar\* appears in frame name SYSTEM.")
  (sit-for 3)
  ;; condition # 4(a) | no-file-visiting buffer
  (display-buffer (get-buffer-create "*NO-FILE-special-buffer-regexp*"))
  (message "NO-FILE buffer existing frame, without other windows.")
  (sit-for 3)
  ;; condition # 2(a) | file-visiting buffer
  (lawlist-find-file "foo.txt")
  (set-frame-height (selected-frame) 20)
  (set-frame-width (selected-frame) 80)
  (set-frame-position (selected-frame) 100 100)
  (message "\"foo.txt\" appears in frame name MAIN.")
  (sit-for 3)
  ;; condition # 1 | file-visiting buffer
  (lawlist-find-file "doe.org")
  (set-frame-height (selected-frame) 20)
  (set-frame-width (selected-frame) 80)
  (set-frame-position (selected-frame) 200 200)
  (message "\"doe.org\" appears in frame name ORG.")
  (sit-for 3)
  ;; condition # 4(b) | file-visiting buffer
  (lawlist-find-file "*FILE-special-buffer-regexp*")
  (message "FILE buffer existing frame, without other windows.")
  (sit-for 3)
  ;; condition # 6 | no-file-visiting buffer default display
  (calendar)
  (message "Default for no-file-visiting-buffers.")
  (sit-for 3)
  ;; condition # 5 | file-visiting buffer with no pre-defined regexp.
  (lawlist-find-file "*FILE-undefined-regexp*")
  (set-frame-height (selected-frame) 20)
  (set-frame-width (selected-frame) 80)
  (set-frame-position (selected-frame) 300 300)
  (message "\*IS\* buffer-filename.  \*NOT\* defined by any particular regexp.")
  (sit-for 3)
  ;; condition # 2(b) | no-file-visiting buffer
  (display-buffer (get-buffer-create "*NO-FILE-main-buffer-regexp*"))
  (set-frame-height (selected-frame) 20)
  (set-frame-width (selected-frame) 80)
  (set-frame-position (selected-frame) 400 400)
  (message "\*NOT\* buffer-filename.  \*IS\* defined by main-buffer-regexp.")
  (sit-for 3)
  (kill-buffer "*bar*")
  (kill-buffer "foo.txt")
  (kill-buffer "doe.org")
  (kill-buffer "*FILE-undefined-regexp*")
  (kill-buffer "*NO-FILE-main-buffer-regexp*")
  (kill-buffer "*Calendar*")
  (kill-buffer "*FILE-special-buffer-regexp*")
  (kill-buffer "*NO-FILE-special-buffer-regexp*")
  (make-frame)
  (delete-frame (get-frame "SYSTEM"))
  (delete-frame (get-frame "MAIN"))
  (delete-frame (get-frame "ORG"))
  (delete-frame (get-frame "MISCELLANEOUS"))
  (message "THE END."))

(defvar regexp-frame-names "^\\(?:MAIN\\|SYSTEM\\|ORG\\|MISCELLANEOUS\\)$"
    "Regexp matching frames with specific names.")

(defvar system-buffer-regexp nil
  "Regexp of file / buffer names displayed in frame `SYSTEM`.")
(setq system-buffer-regexp '("*scratch*" "*bbdb*" "*bar*"))

(defvar main-buffer-regexp nil
  "Regexp of file / buffer names displayed in frame `MAIN`.")
(setq main-buffer-regexp
  '("\\.txt" "\\.tex" "\\.el" "\\.yasnippet" "\\*NO-FILE-main-buffer-regexp\\*"))

(defvar org-buffer-regexp nil
  "Regexp of file / buffer names displayed in frame  `ORG`.")
(setq org-buffer-regexp '("[*]TODO[*]" "[*]Org Agenda[*]" "\\.org_archive" "\\.org"))

(defvar special-buffer-regexp nil
  "Regexp of file / buffer names that will .")
(setq special-buffer-regexp
  '("[*]NO-FILE-special-buffer-regexp[*]" "*FILE-special-buffer-regexp*"))

(defvar buffer-filename nil)

(defun lawlist-find-file (&optional buffer-filename)
  "With assistance from the display-buffer-alist, locate or create a specific frame,
  and then open the file."
  (interactive)
  (unless buffer-filename (setq buffer-filename
    (read-file-name "Select File: " "~/" nil nil nil nil)))
  ;; If using a version of Emacs built `--with-ns`, then user may substitute:
  ;; (unless buffer-filename (setq buffer-filename
  ;;   (ns-read-file-name "Select File:" "~/" t nil nil)))
  (if buffer-filename
    (display-buffer (find-file-noselect buffer-filename))))

(setq display-buffer-alist '((".*" . (lawlist-display-buffer-pop-up-frame))))

(defun lawlist-display-buffer-pop-up-frame (buffer alist)
  (cond
    ;; condition # 1 -- either file-visiting or no-file buffers
    ((regexp-match-p org-buffer-regexp (buffer-name buffer))
      (if (frame-exists "ORG")
          (switch-to-frame "ORG")
        ;; If unnamed frame exists, then take control of it.
        (catch 'break (dolist (frame (frame-list))
          (if (not (string-match regexp-frame-names (frame-parameter frame 'name)))
            (throw 'break (progn
              (switch-to-frame (frame-parameter frame 'name))
              (set-frame-name "ORG"))))))
        ;; If dolist found no unnamed frame, then create / name it.
        (if (not (frame-exists "ORG"))
          (progn
            (make-frame)
            (set-frame-name "ORG"))) )
      (if (and (featurep 'init-frames) frame-bufs-mode)
        (frame-bufs-add-buffer (get-buffer buffer) (selected-frame)))
      (set-window-buffer (frame-selected-window) (buffer-name buffer)))
    ;; condition # 2 -- either file-visiting or no-file buffers
    ((regexp-match-p main-buffer-regexp (buffer-name buffer))
      (if (frame-exists "MAIN")
          (switch-to-frame "MAIN")
        ;; If unnamed frame exists, then take control of it.
        (catch 'break (dolist (frame (frame-list))
          (if (not (string-match regexp-frame-names (frame-parameter frame 'name)))
            (throw 'break (progn
              (switch-to-frame (frame-parameter frame 'name))
              (set-frame-name "MAIN"))))))
        ;; If dolist found no unnamed frame, then create / name it.
        (if (not (frame-exists "MAIN"))
          (progn
            (make-frame)
            (set-frame-name "MAIN"))) )
      (if (and (featurep 'init-frames) frame-bufs-mode)
        (frame-bufs-add-buffer (get-buffer buffer) (selected-frame)))
      (set-window-buffer (frame-selected-window) (buffer-name buffer)))
    ;; condition # 3 -- either file-visiting or no-file buffers
    ((regexp-match-p system-buffer-regexp (buffer-name buffer))
      (if (frame-exists "SYSTEM")
          (switch-to-frame "SYSTEM")
        ;; If unnamed frame exists, then take control of it.
        (catch 'break (dolist (frame (frame-list))
          (if (not (string-match regexp-frame-names (frame-parameter frame 'name)))
            (throw 'break (progn
              (switch-to-frame (frame-parameter frame 'name))
              (set-frame-name "SYSTEM"))))))
        ;; If dolist found no unnamed frame, then create / name it.
        (if (not (frame-exists "SYSTEM"))
          (progn
            (make-frame)
            (set-frame-name "SYSTEM"))) )
      (if (and (featurep 'init-frames) frame-bufs-mode)
        (frame-bufs-add-buffer (get-buffer buffer) (selected-frame)))
      (set-window-buffer (frame-selected-window) (buffer-name buffer)))
    ;; condition # 4
    ;; display buffer in the existing frame, without other windows
    ((regexp-match-p special-buffer-regexp (buffer-name buffer))
      (if (and (featurep 'init-frames) frame-bufs-mode)
        (frame-bufs-add-buffer (get-buffer buffer) (selected-frame)))
      (set-window-buffer (frame-selected-window) (buffer-name buffer)))
    ;; condition # 5
    ;; file-visiting buffers that do NOT match any pre-defined regexp
    ((and (not (regexp-match-p org-buffer-regexp (buffer-name buffer)))
          (not (regexp-match-p main-buffer-regexp (buffer-name buffer)))
          (not (regexp-match-p system-buffer-regexp (buffer-name buffer)))
          (not (regexp-match-p special-buffer-regexp (buffer-name buffer)))
          buffer-filename )
      (if (frame-exists "MISCELLANEOUS")
          (switch-to-frame "MISCELLANEOUS")
        ;; If unnamed frame exists, then take control of it.
        (catch 'break (dolist (frame (frame-list))
          (if (not (string-match regexp-frame-names (frame-parameter frame 'name)))
            (throw 'break (progn
              (switch-to-frame (frame-parameter frame 'name))
              (set-frame-name "MISCELLANEOUS"))))))
        ;; If dolist found no unnamed frame, then create / name it.
        (if (not (frame-exists "MISCELLANEOUS"))
          (progn
            (make-frame)
            (set-frame-name "MISCELLANEOUS"))))
      (if (and (featurep 'init-frames) frame-bufs-mode)
        (frame-bufs-add-buffer (get-buffer buffer) (selected-frame)))
      (set-window-buffer (frame-selected-window) (buffer-name buffer)))
    ;; condition # 6
    ;; default display for no-file-visiting buffers
    (t nil) ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; GENERIC REGEXP FUNCTION ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun regexp-match-p (regexps string)
"Before the lisp function, define the variable like this:\n
(defvar example-regexp nil
  \"Regexps matching `buffer-name buffer` for frame name `SYSTEM`.\")
    (setq example-regexp '(\"\\(\\*foo\\*\\|\\*bar\\*\\)\"))
\nWithin the lisp function, use something like this:\n
(regexp-match-p example-regexp (buffer-name buffer))
\nOr, this:\n
(regexp-match-p example-regexp buffer-filename)"
  (catch 'matched
    (dolist (regexp regexps)
      (if (string-match regexp string)
        (throw 'matched t)))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; GENERIC FRAME UTILITIES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defun frame-exists (frame-name)
  (not (eq nil (get-frame frame-name))))

(defun get-frame-name (&optional frame)
  "Return the string that names FRAME (a frame).  Default is selected frame."
  (unless frame (setq frame (selected-frame)))
  (if (framep frame)
      (cdr (assq 'name (frame-parameters frame)))
    (error "Function `get-frame-name': Argument not a frame: `%s'" frame)))

(defun get-frame (frame)
  "Return a frame, if any, named FRAME (a frame or a string).
  If none, return nil.
  If FRAME is a frame, it is returned."
  (cond ((framep frame) frame)
        ((stringp frame)
         (catch 'get-a-frame-found
           (dolist (fr (frame-list))
             (when (string= frame (get-frame-name fr))
               (throw 'get-a-frame-found fr)))
           nil))
        (t
         (error
          "Function `get-frame-name': Arg neither a string nor a frame: `%s'"
          frame))))

(defun switch-to-frame (frame-name)
  (let ((frames (frame-list)))
    (catch 'break
      (while frames
        (let ((frame (car frames)))
          (if (equal (frame-parameter frame 'name) frame-name)
              (throw 'break (select-frame-set-input-focus frame))
            (setq frames (cdr frames))))))))

;;;;;;;;;;;;;;;;;;;;;;;; IF BUILT --with-ns, THEN ALSO USE ;;;;;;;;;;;;;;;;;;;;;;;;;;

(defalias 'ns-find-file 'lawlist-ns-find-file)

(defun lawlist-ns-find-file ()
  "Do a `find-file' with the `ns-input-file' as argument."
  (interactive)
  (let* ((f (file-truename
    (expand-file-name (pop ns-input-file)
      command-line-default-directory)))
    (file (find-file-noselect f))
    (bufwin1 (get-buffer-window file 'visible))
    (bufwin2 (get-buffer-window "*scratch*" 'visible)))
  (cond
    (bufwin1
      (select-frame (window-frame bufwin1))
      (raise-frame (window-frame bufwin1))
      (select-window bufwin1))
    ((and (eq ns-pop-up-frames 'fresh) bufwin2)
      (ns-hide-emacs 'activate)
      (select-frame (window-frame bufwin2))
      (raise-frame (window-frame bufwin2))
      (select-window bufwin2)
      (lawlist-find-file f))
    (t
      (ns-hide-emacs 'activate)
      (lawlist-find-file f)))))