Programming special windows - ECB - the Emacs Code Browser
Description: ECB - the Emacs Code Browser

Next: Possible layout-outlines, Previous: Programming a new layout, Up: The layout-engine

#### 11.5.2 All aspects of programming special windows

ECB offers a flexible programmable layout-engine for other packages to display their own contents and informations in special ECB-windows. An example could be a graphical debugger which offers a special window for displaying local variables and another special window for messages from the debugger-process (like JDEbug of JDEE1).

This section explains all aspects of programming new special windows, adding them to a new layout and synchronizing them with edit-window of ECB. This can be done best with an easy example which nevertheless covers all necessary aspects to be a good example and skeleton for complex tools (like a graphical debugger) which want to use the layout-engine of ECB do display their own information.

IMPORTANT: See tree-buffer for a full documentation of the library tree-buffer.el which can be used for programming a new special window as a tree!

Here comes the example:

##### 11.5.2.1 The outline of the example layout:

       -------------------------------------------------------
       |Bufferinfo for <filename>:            |[prior]       |
       |Type: file                            |[next]        |
       |Size: 23456                           |              |
       |Modes: rw-rw-rw-                      |              |
       |-----------------------------------------------------|
       |                                                     |
       |                                                     |
       |                                                     |
       |                                                     |
       |                 edit-window                         |
       |                                                     |
       |                                                     |
       |                                                     |
       |                                                     |
       -------------------------------------------------------
       |                                                     |
       |                 compilation-window                  |
       |                                                     |
       -------------------------------------------------------

##### 11.5.2.2 The description of the layout-contents

The top-left window always displays informations about the current buffer in the selected edit-window. This window demonstrates how autom. synchronizing a special window/buffer of a layout with current edit-window.

The top-right window contains an read-only “action-buffer” and offers two buttons which can be used with the middle mouse-button to scroll the edit-window. This is not very senseful but it demonstrates how to control the edit-window with actions performed in a special window/buffer of a layout.

(If you have not set a compilation-window in `ecb-compile-window-height` then the layout contains no persistent compilation window and the other windows get a little more place).

##### 11.5.2.3 The example code

Now let have us a look at the several parts of the Elisp-program needed to program this new example layout. ECB contains a library `ecb-examples.el` which contains the full working code of this example. To test this example and to play with it you can load this library into Emacs (with `load-library` for example) and then calling `ecb-change-layout` (bound to `C-c . lc`) and inserting “example-layout1” as layout-name. An alternative is calling `ecb-examples-activate` and `ecb-examples-deactivate`. For details see file `ecb-examples.el`.

The following steps only contain code-skeletons to demonstrate the principle. The full working code is available in `ecb-examples.el`.

##### 11.5.2.4 The bufferinfo buffer of the example

The name of the bufferinfo buffer:

       (defconst ecb-examples-bufferinfo-buffer-name " *ECB buffer info*")

Two helper functions for displaying infos in a special buffer:

       (defun ecb-examples-print-file-attributes (buffer filename)
         (ecb-with-readonly-buffer buffer
           (erase-buffer)
           (insert (format "Bufferinfo for %s:\n\n" filename))
           ;; insert with the function `file-attributes' some
           ;; informations about FILENAME.
           ))

       (defun ecb-examples-print-non-filebuffer (buffer buffer-name)
         (ecb-with-readonly-buffer buffer
           (erase-buffer)
           ;; analogous to `ecb-examples-print-file-attributes'
           ))

For synchronizing the bufferinfo window with the current source-buffer we first introduce some three options which are very typical for a special ECB-window which should be synchronized with current buffer in the edit-area.

The :type of the first two options is essential and MUST NOT be defined differently, because the macro `defecb-autocontrol/sync-function` (s.b.) and the function `ecb-activate-ecb-autocontrol-function` (s.b.) expects exactly these option-types.

The third one for an own hook running after synchronizing is not essential but mostly useful for users who wants to do some own stuff.

Here come the three options - for detailled docstrings take a look into the file `ecb-examples.el`:

       (defcustom ecb-examples-bufferinfo-buffer-sync 'basic
         "*Synchronize the bufferinfo buffer automatically with current edit buffer.

       If 'always then the synchronization takes place always a buffer changes in the
       edit window, if nil then never. If a list of major-modes then only if the
       `major-mode' of the new buffer belongs NOT to this list.

       If the special value 'basic is set then ECB uses the setting of the option
       `ecb-basic-buffer-sync'.

       IMPORTANT NOTE: Every time the synchronization is done the hook
       `ecb-bufferinfo-buffer-sync-hook' is evaluated."
         :group 'ecb-examples-bufferinfo
         :type '(radio :tag "Synchronize ECBs example bufferino buffer"
                       (const :tag "use basic value" :value basic)
                       (const :tag "Always" :value always)
                       (const :tag "Never" nil)
                       (repeat :tag "Not with these modes"
                               (symbol :tag "mode"))))

       (defcustom ecb-examples-bufferinfo-buffer-sync-delay 'basic
         "*Time Emacs must be idle before the bufferinfo-buffer is synchronized.
       Synchronizing is done with the current source displayed in the edit window. If
       nil then there is no delay, means synchronization takes place immediately. A
       small value of about 0.25 seconds saves CPU resources and you get even though
       almost the same effect as if you set no delay.

       If the special value 'basic is set then ECB uses the setting of the option
       `ecb-basic-buffer-sync-delay'"
         :group 'ecb-analyse
         :type '(radio (const :tag "use basic value" :value basic)
                       (const :tag "No synchronizing delay" :value nil)
                       (number :tag "Idle time before synchronizing" :value 2))
         :set (function (lambda (symbol value)
                          (set symbol value)
                          (if (and (boundp 'ecb-minor-mode)
                                   (featurep 'ecb-examples)
                                   ecb-minor-mode)
                              (ecb-activate-ecb-autocontrol-function
                               value 'ecb-examples-bufferinfo-buffer-sync))))
         :initialize 'custom-initialize-default)

       (defcustom ecb-examples-bufferinfo-buffer-sync-hook nil
         "Hook run at the end of `ecb-examples-bufferinfo-buffer-sync'.
       See documentation of `ecb-examples-bufferinfo-buffer-sync' for conditions when
       synchronization takes place and so in turn these hooks are evaluated.

       ..."
         :group 'ecb-analyse
         :type 'hook)

Now we define the synchronizing function. IMPORTANT: The main synchronizing function must be defined with the macro `defecb-autocontrol/sync-function`! See the documentation of this function for all details.

The following function synchronizes the bufferinfo buffer with the current buffer of the edit-window if that buffer has changed

See the comments in the code for explanations.

       (defvar ecb-examples-bufferinfo-last-file-buffer nil)

       (defecb-autocontrol/sync-function ecb-examples-bufferinfo-buffer-sync
           ecb-examples-bufferinfo-buffer-name ecb-examples-bufferinfo-buffer-sync t
         "Synchronizes the buffer-info buffer with current source if changed.
       Can be called interactively but normally this should not be necessary because
       it will be called autom. by the internal synchronizing mechanism of ECB."

         ;; The macro `defecb-autocontrol/sync-function' does a lot for our
         ;; conveniance:

         ;; 1) here we can be sure that the buffer with name
         ;; `ecb-examples-bufferinfo-buffer-name' is displayed in a window of
         ;; `ecb-frame' because the macro `defecb-autocontrol/sync-function'
         ;; encapsulates automatically the following code within
         ;; `ecb-do-if-buffer-visible-in-ecb-frame' and this macro binds locally the
         ;; variables visible-buffer and visible-window: visible-window:=
         ;; (get-buffer-window ecb-examples-bufferinfo-buffer-name) visible-buffer:=
         ;; (get-buffer ecb-examples-bufferinfo-buffer-name)

         ;; 2) The macro `defecb-autocontrol/sync-function' automatically takes care of
         ;;    the setting of option `ecb-examples-bufferinfo-buffer-sync' and runs the
         ;;    following code only when the related conditions are true

         ;; 3) The generated function has one optional argument FORCE which can be used
         ;; in the code below.

         ;; 4) The macro `defecb-autocontrol/sync-function' makes this synchronizing
         ;;    function interactive

         ;; For details please read the documentation of
         ;; `defecb-autocontrol/sync-function'!

         ;; synchronize only when point stays in one of the edit-window.
         (when (ecb-point-in-edit-window-number)

           ;; we need the file-name of indirect-buffers too (if the base-buffer is a
           ;; file-buffer), therefore we use `ecb-buffer-file-name' (see the docstring
           ;; of this function)
           (let ((filename (ecb-buffer-file-name (current-buffer))))

             (if (and filename (ecb-buffer-or-file-readable-p filename))

                 ;; synchronizing for real filesource-buffers or indirect buffers of
                 ;; real file buffers

                   ;; Let us be smart: We synchronize only if sourcebuffer has changed
                   ;; or if the argument FORCE is not nil
                   (when (or force
                             (not (equal (current-buffer)
                                         ecb-examples-bufferinfo-last-file-buffer)))
                     ;; set new last-file-buffer so we can check next time if changed
                     (setq ecb-examples-bufferinfo-last-file-buffer (current-buffer))
                     ;; we display the file-infos for current source-buffer
                     (ecb-examples-print-file-attributes visible-buffer filename))

               ;; what should we do for non file buffers like help-buffers etc...
               (setq ecb-examples-bufferinfo-last-file-buffer nil)
               (ecb-examples-print-non-filebuffer visible-buffer
                                                  (buffer-name (current-buffer)))))

           ;; Now lets run the hooks in `ecb-examples-bufferinfo-buffer-sync-hook'
           (run-hooks 'ecb-examples-bufferinfo-buffer-sync-hook)))

Two conveniance commands for the user:

       (defun ecb-maximize-bufferinfo-window ()
         "Maximize the bufferinfo-window.
       I.e. delete all other ECB-windows, so only one ECB-window and the
       edit-window\(s) are visible \(and maybe a compile-window). Works
       also if the ECB-analyse-window is not visible in current layout."
         (interactive)
         (ecb-display-one-ecb-buffer ecb-examples-bufferinfo-buffer-name))

       (defun ecb-goto-bufferinfo-window ()
         "Make the bufferinfo-window the current window."
         (interactive)
         (ecb-goto-ecb-window ecb-examples-bufferinfo-buffer-name))

##### 11.5.2.5 Synchronizing the bufferinfo-buffer automatically

By using the macro `defecb-autocontrol/sync-function` for defining our synchronizing function we have already registered this function as a synchronizing function managed by ECB:

The only thing we need is to tell ECB when this function should run - for this we have already defined the first two options in our example (s.a.). ECB offers to functions for

- `ecb-activate-ecb-autocontrol-function`: Activate/start synchronizing
- `ecb-stop-autocontrol/sync-function`: Stop synchronizing

It's smart to activate the synchronizing function within the dedicator-function. See the comment within the following function.

The function which makes the bufferinfo-buffer dedicated to a window and registers the new special window/buffer at ECB.

       (defecb-window-dedicator-to-ecb-buffer ecb-examples-set-bufferinfo-buffer
           ecb-examples-bufferinfo-buffer-name nil
         "Set the buffer in the current window to the bufferinfo-buffer and make this
       window dedicated for this buffer. Makes the buffer read-only."

         ;; activating the synchronization of the bufferinfo-window:
         ;; `ecb-activate-ecb-autocontrol-function' takes care of the possible
         ;; settings in `ecb-examples-bufferinfo-buffer-sync-delay'. Therefore we do
         ;; it here because then changes in ecb-examples-bufferinfo-buffer-sync-delay
         ;; are taken into account each time the bufferinfo buffer is set in the
         ;; layout (after each hiding/showing the ecb-window, each redrawing the
         ;; layout deactivating/activating ECB)
         (ecb-activate-ecb-autocontrol-function ecb-examples-bufferinfo-buffer-sync-delay
                                                'ecb-examples-bufferinfo-buffer-sync)

         (switch-to-buffer (get-buffer-create ecb-examples-bufferinfo-buffer-name))
         (setq buffer-read-only t))

This is all what we need for the special bufferinfo buffer. We have demonstrated already three of the important functions/macros of the layout-engine API of ECB: `ecb-with-readonly-buffer`, `defecb-autocontrol/sync-function` and `defecb-window-dedicator-to-ecb-buffer` (see The layout-engine API. Especially the second macro is a MUST for programming good synchronizing functions which do not waste CPU-power! We have also shown how to start/activate a synchronizing function by `ecb-activate-ecb-autocontrol-function`. Later we will see how to stop it.

##### 11.5.2.6 The action buffer of the example

The name of the action-buffer:

       (defconst ecb-examples-action-buffer-name " *ECB action buffer*")

Two helper functions for creating a readonly action-buffer with a special local key-map for the middle-mouse-button and two buttons [prior] and [next]:

       (defun ecb-examples-insert-text-in-action-buffer (text)
         (let ((p (point)))
           (insert text)
           (put-text-property p (+ p (length text)) 'mouse-face
                                                    'highlight)))

       (defun ecb-examples-action-buffer-create ()
         (save-excursion
           (if (get-buffer ecb-examples-action-buffer-name)
               (get-buffer ecb-examples-action-buffer-name)

             (set-buffer (get-buffer-create
                           ecb-examples-action-buffer-name))

             ;; we setup a local key-map and bind middle-mouse-button
             ;; see ecb-examples.el for the full code

             ;; insert the action buttons [prior] and [next] and
             ;; make it read-only

             (ecb-with-readonly-buffer (current-buffer)
               (erase-buffer)
               (ecb-examples-insert-text-in-action-buffer "[prior]")
               ;; analogous for the [next] button
               )

             (current-buffer))))

The function which performs the actions in the action-buffer if clicked with the middle-mouse button onto a button [next] or [prior].

       (defun ecb-examples-action-buffer-clicked (e)
         (interactive "e")
         (mouse-set-point e)
         (let ((line (buffer-substring (ecb-line-beginning-pos)
                                       (ecb-line-end-pos))))
           (cond ((string-match "prior" line)
                  (ecb-select-edit-window)
                  (call-interactively 'scroll-down))
                 ((string-match "next" line)
                  ;; analogous for [next]
                  ))))

Two conveniance-commands for the user:

       (defun ecb-maximize-action-window ()
         "Maximize the action-window.
       I.e. delete all other ECB-windows, so only one ECB-window and the
       edit-window\(s) are visible \(and maybe a compile-window). Works
       also if the ECB-analyse-window is not visible in current layout."
         (interactive)
         (ecb-display-one-ecb-buffer ecb-examples-action-buffer-name))

       (defun ecb-goto-action-window ()
         "Make the action-window the current window."
         (interactive)
         (ecb-goto-ecb-window ecb-examples-action-buffer-name))

The function which makes the action-buffer dedicated to a window and registers it at ECB.

       (defecb-window-dedicator-to-ecb-buffer ecb-examples-set-action-buffer
           ecb-examples-action-buffer-name nil
         "Set the buffer in the current window to the action-buffer
       and make this window dedicated for this buffer."
         (switch-to-buffer (buffer-name (ecb-examples-action-buffer-create))))

We do not need more code for the action buffer. All of the code is standard emacs-lisp which would also needed if used without ECB.

##### 11.5.2.7 Adding the bufferinfo- and action-buffer to a new layout

Now we add the bufferinfo- and the action-buffer to a new layout of type top with name “example-layout1”:

       (ecb-layout-define "example-layout1" top

         ;; dedicating the bufferinfo window to the bufferinfo-buffer
         (ecb-examples-set-bufferinfo-buffer)

         ;; creating the action-window
         (ecb-split-hor 0.75)

         ;; dedicate the action window to the action-buffer
         (ecb-examples-set-action-buffer)

         ;; select the edit-window
         (select-window (next-window)))

This all what we need to define the new layout. See Programming a new layout for more details of the pure layout-programming task.

##### 11.5.2.8 Activating and deactivating new layouts

Because a set of new special windows integrated in a new layout is often just the GUI of a complete tool (like a graphical debugger) we demonstrate here the complete activation and deactivation of such a tool or at least of the tool-GUI. We decide that the GUI of our example “tool” needs a compile-window with height 5 lines and the height of the special windows “row” on top should be exactly 6 lines (normally width and height of the special windows should be a fraction of the frame, but here we use 6 lines2

Here comes the (de)activation code.

The code for saving and restoring the state before activation (the full code is available in `ecb-examples.el`:

       (defun ecb-examples-preactivation-state(action)
         (cond ((equal action 'save)
                ;; code for saving the state
                )
               ((equal action 'restore)
               ;; code for restoring the state
               )))

The following function activates the GUI of our example tool:

       (defun ecb-examples-activate ()
         "Activate the new layout \"example-layout1\".
       Acivates the function `ecb-examples-bufferinfo-buffer-sync', set
       `ecb-compile-window-height' to 5 and `ecb-windows-height' to 6. The
       preactivation-state is saved and will be restored by
       `ecb-examples-deactivate'."
         (interactive)

         (assert (featurep 'ecb) nil
                 "ECB must be loaded!")
         (assert ecb-minor-mode nil
                 "ECB must be activated!")
         (assert (equal (selected-frame) ecb-frame) nil
                 "The ECB-frame must be selected!")
         (assert (not (ecb-string= ecb-layout-name "example-layout1")) nil
                 "The examples-layout1 is already active!")

         ;; saving the state
         (ecb-examples-preactivation-state 'save)

         ;; switch to our prefered layout
         (setq ecb-windows-height 6)
         (setq ecb-compile-window-height 8)
         (let ((ecb-change-layout-preserves-compwin-state nil))
           ;; activating the synchronization of the bufferinfo-window is done in the
           ;; dedicator-function (see `ecb-examples-set-bufferinfo-buffer' for the
           ;; reason). So the synchronizing will be activated implicitly with the
           ;; layout-switch because this redraws the layout and this calls all
           ;; dedicator-functions.
           (ecb-layout-switch "example-layout1")))

This function deactivates the GUI of our example-tool and restores the state as before activation:

       (defun ecb-examples-deactivate ()
         "Deactivate the new layout \"example-layout1\".
       Stops `ecb-examples-bufferinfo-buffer-sync' and restore the state
       as before activation."
         (interactive)

         (assert (featurep 'ecb) nil
                 "ECB must be loaded!")
         (assert ecb-minor-mode nil
                 "ECB must be activated!")
         (assert (equal (selected-frame) ecb-frame) nil
                 "The ECB-frame must be selected!")
         (assert (ecb-string= ecb-layout-name "example-layout1") nil
                 "The example-layout1 is not active!")

         ;; we stop here the synchronization.
         (ecb-stop-autocontrol/sync-function 'ecb-examples-bufferinfo-buffer-sync)

         (ecb-examples-preactivation-state 'restore)

         (ecb-layout-switch ecb-layout-name))

Now we have all code for the new layout and the new layout-buffers. The example is ready for use; just load `ecb-examples.el` (s.a.).

#### Footnotes

[1] JDEE is available at http://jdee.sunsite.dk/

[2] You can change the code in the file `ecb-examples.el` to use a frame-fraction of 0.2 instead of 6 hard lines, just try it!