8

I sometimes want to display information in Help buffer style, so I have been using code like this:

(with-help-window (help-buffer)
  (princ "Type q to exit this help buffer.\n\n")
  (princ result))

This works fine, but the help window only uses half of my frame. I normally split my frame horizontally, to give two tall windows. The displayed help buffer uses one of the two windows.

I would rather use the whole frame in some cases, to display more information and to reduce the number of times I need to page down through the displayed information. The problem to solve is how to temporarily use the whole frame for the with-help-window call, and to automatically restore the original buffers/window sizes when I type "q" in the help window.

How can I best achieve this goal? I think I'm looking for something like this:

(with-FULL-FRAME-help-window (help-buffer)
   ...)

I've looked at winner-mode, bookmarks, saving layouts to registers, the various (and powerful, but complex) (display-buffer ...) methods. Most of them seem slightly off-target to my desired intent because they tend to correct/restore a layout after a full frame display operation. And it seems to me that many of them require me to manually restore my window layout (which I would prefer not to do).

I'm wondering if anyone has heard of a way to solve this simply. I'm hoping for something simple like these possible approaches, where I can override something with a let frame...

(let ((help-window-width-display-option fullwidth))
  (with-help-window (help-buffer)
    ...))

Or this kind of approach, which I don't know how to do yet, and which looks somewhat difficult/tricky for my current skill level.

(let ((save original configuration somehow)
  (delete-other-windows)
  (with-help-window (help-buffer)
     ...)
  ;; somehow, when I type "q" in the help buffer
  ;; - catch that action in code after the buffer is killed
  ;; - and restore the original window configuration
  )

It seems to me the key problem for me to solve is how to automatically restore the original window configuration when I type "q" in the temporary help-mode buffer. Thanks

  • One idea would be to use display-buffer-pop-up-frame: gnu.org/software/emacs/manual/html_node/elisp/… Another idea idea would be to issue a make-frame while using display-buffer with a custom function to target that new frame. If you are interested in locating and targeting an existing frame, then take a look at this example: stackoverflow.com/questions/18346785/… – lawlist Jun 18 '16 at 16:12
  • Here is an idea of how to save and restore your window configuration so that you can use the existing frame: emacs.stackexchange.com/a/2714/2287 If you find that like like certain window configurations, you may wish to consider setting something up that is more elaborate -- there are several libraries that deal with saving and switching between various window configurations. – lawlist Jun 18 '16 at 16:24
  • As usual lawlist, thank you for your help. I have already tried display-buffer-pop-up-frame, since it is quite close to what I seek. But... the frame pops up in another place (not my current frame), and I must dispatch it with cmd-w, not "q" in help-style. Saving / restoring window configs is not the underlying problem. Currently I'm leaning toward cloning and modifying the source of with-help-window to give it an option that I can let-bind, or wrap with with a defmacro or something. I smile at how picky we emacs people are at wanting exactly what we want from Emacs. – Kevin Jun 18 '16 at 16:41
  • After reading more in help.el, the solution seems to be buried somewhere in help-return-method, quit-window, the quit-restore window parameter, and probably some custom code to set/use all those things to create the desired effect. – Kevin Jun 18 '16 at 17:26
5

Example #1: The keyboard shortcut q in the help-mode buffer comes from the special-mode-map that is incorporated into the help-mode-map. The default value is quit-window, which offers only four (4) possible actions: "According to information stored in WINDOW’s quit-restore window parameter either (1) delete WINDOW and its frame, (2) delete WINDOW, (3) restore the buffer previously displayed in WINDOW, or (4) make WINDOW display some other buffer than the present one. If non-nil, reset quit-restore parameter to nil." [See doc-string: M-x describe-function RET quit-window RET]

Here is an outline of what this example does:

  • Let-bind the variable help-window-select to t so that the *Help* window is selected.

  • Let-bind the current window configuration to a temporary variable called config.

  • Generate the *Help* window.

  • Store the previous window configuration -- config -- in a local-variable called my-stored-win-config.

  • Create a local key assignment for the letter q, which is bound to my-restore-win-config. [This local assignment trumps/shadows the previous assignment of quit-window.]

  • Delete other windows.

  • Press the letter q to restore the prior window configuration, and kill the *Help* buffer.

(defvar my-stored-win-config nil)
(make-variable-buffer-local 'my-stored-win-config)

(defun my-restore-win-config ()
  (interactive)
  (when my-stored-win-config
    (set-window-configuration my-stored-win-config)
    (kill-buffer "*Help*")))

The following snippet is a sample usage, but is not a complete interactive function. It can be evaluated in the *scratch* buffer to see it in action.

(let ((help-window-select t)
      (config (current-window-configuration)))
  (with-help-window (help-buffer)
    (princ "Type q to kill this *Help* buffer and restore prior window configuration."))
  (with-current-buffer "*Help*"
    (setq my-stored-win-config config)
    (local-set-key "q" 'my-restore-win-config))
  (delete-other-windows))

Example #2:

Here is a self-contained macro that does everything as the above example, which deals with three possible situations relating to existing hook values -- e.g., nil, symbol, or, list of symbols.

(defmacro help-window-full-frame (buffer-name &rest body)
"Doc-string."
  (declare (indent 1) (debug t))
  `(progn
    (set-marker help-window-point-marker nil)
      (let* (
          (help-window-select t)
          (foo
            (lambda ()
              (set (make-local-variable 'window-configuration)
                (current-window-configuration))
              (local-set-key "q"
                (lambda ()
                  (interactive)
                  (when window-configuration
                    ;; Record the `current-buffer' before it gets buried.
                    (let ((cb (current-buffer)))
                      (set-window-configuration window-configuration)
                      (kill-buffer cb)))))))
          ;; Preserve the original hook values by let-binding them in advance.
          ;; Otherwise, `add-to-list' would alter the global value.
          (temp-buffer-window-setup-hook temp-buffer-window-setup-hook)
          (temp-buffer-window-show-hook temp-buffer-window-show-hook)
          (temp-buffer-window-setup-hook
            (cond
              ((null temp-buffer-window-setup-hook)
                (list 'help-mode-setup foo))
              ((and
                  (not (null temp-buffer-window-setup-hook))
                  (listp temp-buffer-window-setup-hook))
                (add-to-list 'temp-buffer-window-setup-hook foo)
                (add-to-list 'temp-buffer-window-setup-hook 'help-mode-setup))
              ((and
                  (not (null temp-buffer-window-setup-hook))
                  (symbolp temp-buffer-window-setup-hook))
                (list 'help-mode-setup foo temp-buffer-window-setup-hook))))
          (temp-buffer-window-show-hook
            (cond
              ((null temp-buffer-window-show-hook)
                (list 'help-mode-finish 'delete-other-windows))
              ((and
                  (not (null temp-buffer-window-show-hook))
                  (listp temp-buffer-window-show-hook))
                (add-to-list 'temp-buffer-window-show-hook 'delete-other-windows)
                (add-to-list 'temp-buffer-window-show-hook 'help-mode-finish))
              ((and
                  (not (null temp-buffer-window-show-hook))
                  (symbolp temp-buffer-window-show-hook))
                (list
                  'help-mode-finish
                  'delete-other-windows
                  temp-buffer-window-show-hook)))) )
        (with-temp-buffer-window ,buffer-name nil 'help-window-setup (progn ,@body)))))

And here is the sample snippet to evaluate in the *scratch* buffer.

(help-window-full-frame (help-buffer)
  (princ "Type q to kill this *Help* buffer and restore prior window configuration."))
  • Wow, thank you for an excellent answer. I had progressed to save/restore the window config, and had created a my-help-quit function, while trying to rebind the help-map key inside of with-help-window. But it wasn't working. I now see you bind the key inside the Help buffer (not the Help window like I was doing) after the buffer is set up. I guess my binding was clobbered by the buffer setup. A lesson learned. Everything is working now. Many thanks. – Kevin Jun 18 '16 at 23:14
  • There are two (2) opportunities to act directly upon the *Help* buffer before it finishes -- the temp-buffer-window-setup-hook which runs help-mode-setup and then anything else already/previously assigned to the hook; and, then the temp-buffer-window-show-hook which runs help-mode-finish and anything already/previously assigned to the hook. help-mode-setup should remain first in time, but you could add something behind it by binding either one of those aforementioned hooks with custom stuff. In that scenario, you would not need with-current-buffer. – lawlist Jun 18 '16 at 23:49
  • Agreed. I looked at both help-mode-setup and help-mode-finish, but they both ran before the buffer was displayed. The key problem was to redirect the "q" keybinding, and you showed me how to do that in the buffer (not the window, which I was trying to do). PS. I tried to write a solution as (defmacro with-full-frame-help-window, but the macro still requires a separate function to handle the "q" and window restoration action. I will post my completed functions below. – Kevin Jun 18 '16 at 23:58
  • I updated the answer with a second example that uses a self-contained macro that does everything that the first example does. – lawlist Jun 19 '16 at 0:58
  • 1
    This also works for me, to replace the hardcoded "Help" buffer reference to the current buffer, because the restoration lambda is a buffer-local function. ... (kill-buffer (current-buffer)))))). The macro took a buffer-name as argument, and killed "Help", so there could be a problem if the caller used a buffer whose name was different. I modified my macro to remove the buffer-name parameter, and generated/killed the same buffer inside the defmacro. – Kevin Jun 19 '16 at 3:56
3

Based on the excellent answer by @lawlist, here are my completed functions for the next guy...

;; a tmp buffer-local place that gets destroyed with the help buffer
(defvar kwj-v-window-config-saved nil)
(make-variable-buffer-local 'kwj-v-window-config-saved)

(defun kwj-help-window-full-frame (string)
  "Show STRING in a help buffer using the full current frame."
  (let (original-layout)
    ;; set this before Help changes the config
    (setq original-layout (current-window-configuration))
    (with-help-window (help-buffer)
      (princ "Type q to exit this help buffer.\n\n")
      (princ string))
    (with-current-buffer "*Help*"
      ;; save layout in buffer local var that gets deleted
      (setq kwj-v-window-config-saved original-layout)
      ;; bind key in BUFFER (not in help window above)
      ;; bind key *after* help buf is displayed
      (local-set-key "q" 'kwj-help-window-restore))
    (delete-other-windows)))

(defun kwj-help-window-restore ()
  "Restore original windows after a full frame help display."
  (interactive)
  (set-window-configuration kwj-v-window-config-saved)
  (kill-buffer "*Help*"))

The long chain of comments above, with continued help from @lawlist, resulted in this version of a macro that doesn't require a buffer name, properly treats original setup/show hook lists, and that doesn't cause problems with the "q" key in other Help mode buffers.

(defmacro with-help-window-full-frame (&rest body)
  "Display text in a full-frame help window.
Execute BODY forms to put output into the window, with standard
output directed to the buffer."
  ;;tell indenter about this macro name
  (declare (indent 1))
  ;; must use a buffer string name here, not the buffer itself
  `(let ((mybuf ,(buffer-name (get-buffer-create "Full Frame Help")))
         ;;`(let ((mybuf ,(help-buffer))
         mysetup tmpsetup tmpshow)
     ;; save a copy of original hooks
     (setq tmpsetup (copy-list temp-buffer-window-setup-hook))
     (setq tmpshow (copy-list temp-buffer-window-show-hook))

     ;; create window config store and restore functions
     ;; credit to @lawlist on stackoverflow for this embedded setup
     (setq mysetup
           (lambda ()
             ;; store original window configuration
             (set (make-local-variable 'orig-win-config)
                  (current-window-configuration))
             ;; bind q to the window restore function
             (local-set-key
              "q"
              (lambda ()
                (interactive)
                ;; q is shared by all Help-mode buffers
                ;; so guard those that did not set up orig-win-config
                (when (boundp 'orig-win-config)
                  (set-window-configuration orig-win-config))
                (kill-buffer (current-buffer))))))

     ;; Add to help setup hooks. Keep original hook functions if any
     ;; delete-dups destructively hacks our tmp copy, not original hooklists
     (push mysetup tmpsetup)          ;order critical here
     (push 'help-mode-setup tmpsetup) ;this must be first in hook
     (delete-dups tmpsetup)

     (push 'help-mode-finish tmpshow) ;order not important here
     (push 'delete-other-windows tmpshow)
     (delete-dups tmpshow)

     ;; shadow the original hooks with our copies for the display call
     (let ((temp-buffer-window-setup-hook tmpsetup)
           (temp-buffer-window-show-hook tmpshow))

       ;; show buf with locally embedded window restore function
       (with-temp-buffer-window mybuf nil
                                'help-window-setup
                                (progn ,@body)))))

Use the macro in this way:

(with-help-window-full-frame
    (princ "Type q to exit this buffer."))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.