-
Notifications
You must be signed in to change notification settings - Fork 23
Writing Extensions
- 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):
- a purpose configuration
- a window layout or a frame layout
- purpose-related behavior
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)
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:
- split, delete and resize windows until the frame looks the way you want it
- use
purpose-toggle-window-purpose-dedicated
to make any window you want purpose-dedicated - use
purpose-save-window-layout
orpurpose-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 callingpurpose-save-window-layout
,purpose-save-window-layout-file
orpurpose-get-window-layout
. -
purpose-set-window-properties-functions
: A hook for setting additional properties whenpurpose-set-window-layout
,purpose-load-window-layout-file
orpurpose-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.
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 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 toswitch-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 inpurpose-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 bypurpose--action-function
. -
purpose-select-buffer-hook
: this hook is called every time Purpose displays and selects a buffer. This hook runs after functions likeswitch-to-buffer
, but not afterdisplay-buffer
. Internally, this hook is ran bypurpose-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 todisplay-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 bypurpose-switch-buffer
-
force-same-window
: sequence used bypurpose-switch-buffer
when called with argument non-nilforce-same-window
-
prefer-same-window
: sequence used bypurpose-pop-buffer-same-window
-
prefer-other-window
: sequence used bypurpose-switch-buffer-other-window
-
prefer-other-frame
: sequence used bypurpose-switch-buffer-other-frame
-
-
purpose-default-action-order
: this variable controls the default action order used bypurpose-select-buffer
andpurpose--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
andalist
. 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 matchespurpose-or-predicate
, it will try to display it withdisplay-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 bypurpose-default-action-sequences
andpurpose-default-action-order
. -
display functions: Purpose provides a number of display functions. These are the
purpose-display-*
functions, which have names likepurpose-display-reuse-purpose-window
andpurpose-display-maybe-pop-up-window
. You can use these functions to build action sequences that suit your needs, and you can also use them inpurpose-special-action-sequences
. -
purpose-change-buffer
: this function is used bypurpose-display-*
functions to perform the actual display of a buffer in a window. If you define a display function, it should usepurpose-change-buffer
. In the time of writing,purpose-change-buffer
just callswindow--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 - ifpurpose-display-at-bottom
opens a window, thenpurpose-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
).
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:
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)))