Skip to content
Jimmy Yuen Ho Wong edited this page Dec 29, 2017 · 8 revisions
  • Note: The page Extensions contains information about existing extensions

Table of Contents

An extension to Purpose is anything that uses Purpose to provide features for Emacs, or improves Purpose's functionality. An extension can be as simple as providing a purpose configuration, such as purpose-x-magit in window-purpose-x.el, or it can be more complex, such as purpose-x-code1.

Writing an extension consists of 3 parts (or less):

  1. a purpose configuration
  2. a window layout or a frame layout
  3. purpose-related behavior

Purpose Configuration

An extension can define a purpose-configuration by creating an instance of purpose-conf and calling purpose-set-extension-configuration. For example, adding a configuration for magit:

(setq magit-purpose-configuration
  (purpose-conf "magit-single"
		:regexp-purposes '(("^\\*magit" . magit))))
(purpose-set-extension-configuration :magit magit-purpose-configuration)

And removing a configuration:

(purpose-del-extension-configuration :magit)

Window/Frame Layout

An extension can change the window or frame layout. For changing a window layout, call purpose-load-window-layout, purpose-load-window-layout-file or purpose-set-window-layout, as shown below:

;; a window layout with one main window (edit), two side windows on the left (dired, buffers),
;; and one side window on the right (ilist)
(setq some-window-layout 
  '(nil
    (0 0 152 35)
    (t
     (0 0 29 35)
     (:purpose dired :purpose-dedicated t :width 0.16 :height 0.5 :edges
	       (0.0 0.0 0.19333333333333333 0.5))
     (:purpose buffers :purpose-dedicated t :width 0.16 :height 0.4722222222222222 :edges
	       (0.0 0.5 0.19333333333333333 0.9722222222222222)))
    (:purpose edit :purpose-dedicated t :width 0.6 :height 0.9722222222222222 :edges
	      (0.19333333333333333 0.0 0.8266666666666667 0.9722222222222222))
    (:purpose ilist :purpose-dedicated t :width 0.15333333333333332 :height 0.9722222222222222 :edges
	      (0.8266666666666667 0.0 1.0133333333333334 0.9722222222222222))))
(purpose-set-window-layout some-window-layout)

To generate a window layout for use in an extension, you should:

  1. split, delete and resize windows until the frame looks the way you want it
  2. use purpose-toggle-window-purpose-dedicated to make any window you want purpose-dedicated
  3. use purpose-save-window-layout or purpose-save-window-layout-file to save the window layout to a file

Additional options:

  • purpose-reset-window-layout: Reset the window layout to the most recently used layout.

  • purpose-get-extra-window-params-function: This variable should contain a function. Use it to save additional window properties when calling purpose-save-window-layout, purpose-save-window-layout-file or purpose-get-window-layout.

  • purpose-set-window-properties-functions: A hook for setting additional properties when purpose-set-window-layout, purpose-load-window-layout-file or purpose-load-window-layout create a window. If you have a window that requires a special buffer, you can use this hook to create the needed buffer and show it in the window. You could also do other stuff, of course.

Frame Layout

A frame layout is a list of window layouts. Frame layouts can be saved and loaded the same as window layouts. purpose-load-frame-layout is parallel to purpose-load-window-layout, purpose-reset-frame-layout is parallel to purpose-reset-window-layout, etc.

Purpose-Related Behavior

Purpose has several hooks, functions and options that can be used to modify its behvior.

  • purpose-select-buffer: this is the general function for switching to a buffer in a Purpose-aware manner. If you define user commands similar to switch-to-buffer, you might want to use it.

  • purpose-display-buffer-functions: this hook is called every time a buffer is displayed in a window. Each function in purpose-display-buffer-functions is called with one argument - the window used for display. Note that the window may not be the selected window. Internally, this hook is ran by purpose--action-function.

  • purpose-select-buffer-hook: this hook is called every time Purpose displays and selects a buffer. This hook runs after functions like switch-to-buffer, but not after display-buffer. Internally, this hook is ran by purpose-select-buffer.

  • purpose-display-fallback: this variable decides what Purpose does when it didn't manage to display a buffer. Possible choices are:

    • pop-up-window: create a new window in current frame
    • pop-up-frame: create a new frame
    • error: signal an error
    • nil: fallback to display-buffer's original (Purpose-less) behavior
  • purpose-action-sequences: this variable controls which display functions Purpose tries for each "action order". You probably shouldn't change this variable, but you can. If you do change it, you must ensure that it contains at least the following action orders:

    • switch-to-buffer: sequence used by purpose-switch-buffer
    • force-same-window: sequence used by purpose-switch-buffer when called with argument non-nil force-same-window
    • prefer-same-window: sequence used by purpose-pop-buffer-same-window
    • prefer-other-window: sequence used by purpose-switch-buffer-other-window
    • prefer-other-frame: sequence used by purpose-switch-buffer-other-frame
  • purpose-default-action-order: this variable controls the default action order used by purpose-select-buffer and purpose--action-function when no action order is specified.

  • purpose-special-action-sequences: this variable lets you control how Purpose displays certain buffers. This is a list of specifications. Each specification is a list that looks like this: (purpose-or-predicate display-function-1 display-function-2 ... display-function-N). purpose-or-predicate is either a purpose, or a function that takes 3 arguments - purpose, buffer and alist. The rest of the specification is an action sequence to use. In this case, the action sequence is (display-function-1 display-function-2 ... display-function-N). When Purpose needs to display a buffer that matches purpose-or-predicate, it will try to display it with display-function-1, display-function-2, etc. until one of the display functions succeeds. If the buffer couldn't be displayed, Purpose will try the regular action sequence indicated by purpose-default-action-sequences and purpose-default-action-order.

  • display functions: Purpose provides a number of display functions. These are the purpose-display-* functions, which have names like purpose-display-reuse-purpose-window and purpose-display-maybe-pop-up-window. You can use these functions to build action sequences that suit your needs, and you can also use them in purpose-special-action-sequences.

  • purpose-change-buffer: this function is used by purpose-display-* functions to perform the actual display of a buffer in a window. If you define a display function, it should use purpose-change-buffer. In the time of writing, purpose-change-buffer just calls window--display-buffer, but it may change in the future.

  • purpose-get-*-window: these are functions for getting the window at top/bottom/left/right, which could come in handy for some extensions. Note that these functions are a bit naive - if purpose-display-at-bottom opens a window, then purpose-display-at-right opens a window, purpose-get-bottom-window will not find the bottom window because it doesn't consume all of the frame's width (the right window isn't above the bottom window).

  • without-purpose, without-purpose-command: use these macros if you want to define some code or a command that needs to deactivate Purpose temporarily.

You can find some more functions by browsing the source code (mainly in purpose-core.el and purpose-switch.el).

Example

For example, here is a small extensions that does 2 things:

  • makes Purpose display all terminal-like buffers in a window at the bottom of the frame
  • opens/closes the terminal when the user hits <f5>

Note that by default all modes that derive from comint-mode have the 'terminal purpose.

(defun my-delete-terminal-windows ()
  "Delete all windows with purpose 'terminal."
  (interactive)
  (mapc #'delete-window (purpose-windows-with-purpose 'terminal)))

(defun my-open-or-delete-terminal ()
  "Toggle window with purpose 'terminal.
Delete 'terminal window if it exists, open it if it doesn't.
If no 'terminal buffer exists, create a `shell' buffer and open it."
  (interactive)
  (if (purpose-windows-with-purpose 'terminal)
      ;; delete window
      (my-delete-terminal-windows)
    (if (purpose-buffers-with-purpose 'terminal)
	;; display existing buffer
	(purpose-switch-buffer-with-purpose-other-window 'terminal)
      ;; create and display `shell' buffer
      (call-interactively #'shell))))

(defun my-auto-dedicate-terminal (window)
  "Dedicate WINDOW's purpose if it is a 'terminal window."
  (when (eql (purpose-window-purpose window) 'terminal)
		(purpose-set-window-purpose-dedicated-p window t)))

;; make purpose display 'terminal windows at bottom
(add-to-list 'purpose-special-action-sequences
	     '(terminal purpose-display-reuse-window-buffer
			purpose-display-reuse-window-purpose
			purpose-display-at-bottom))
;; auto-dedicate 'terminal windows
(add-hook 'purpose-display-buffer-functions #'my-auto-dedicate-terminal)
;; open/close terminal with <f5>
(define-key purpose-mode-map (kbd "<f5>") #'my-open-or-delete-terminal)

It looks like this:

Using Temporary Display Rules

The macros purpose-with-temp-purposes, purpose-with-empty-purposes and purpose-with-additional-purposes allow you to temporarily change the purpose configuration, while the macros purpose-with-temp-display-action, purpose-with-temp-display-actions, purpose-with-additional-display-action and purpose-with-additional-display-actions allow you to temporarily change the special action sequences. Combining these macros lets you run code with special display rules temporarily active for that code only.

For example, let's say we want a command that opens ibuffer in a new frame. That is simple enough:

(defun ibuffer-other-frame ()
  (interactive)
  (purpose-with-temp-purposes '(("*Ibuffer*" . IBUF)) nil nil
    (purpose-with-temp-display-action '(IBUF purpose-display-pop-up-frame)
      (ibuffer))))

Or we can use only purpose-with-temp-display-action:

(defun ibuffer-other-frame ()
  (interactive)
  (purpose-with-temp-display-action
      '((lambda (_purpose buffer _alist)
          (string= (buffer-name buffer) "*Ibuffer*"))
        purpose-display-pop-up-frame)
    (ibuffer)))