
[index entries: _snip_ _arrow_ _label_ ]

_snips_ and _arrows_ library
============================

Collection: mrflow
Files: snips-and-arrows.ss snips-and-arrows-view.ss snips-and-arrows-model.ss

This library allows a programmer to display information about terms in
a DrScheme text editor and its embedded sub-editors.  Textual
information about a given term can be displayed using snips inserted
in the editor next to the term.  Each snip is colored according to a
programmer specified type.  Several snips can exist for a given term
and type.  A relation between two terms can be displayed by drawing an
arrow between the two terms.  Each arrow is colored using a programmer
specified color.  Arrows do not carry any textual information with
them.

The library is fundamentally based on terms.  It is not possible to
display snips or arrows that are not directly related to terms.
However the library does not require terms to be represented by
MzScheme syntax objects.  Terms can be represented by any user-defined
data structure, called hereafter _labels_.  A label represents exactly
one term and the library user is responsible for keeping track of
which term is represented by a given label (by, say, keeping a
reference to the syntax object in the label's data structure), but a
term in an editor can be represented by one or more labels and the
user doesn't have to keep track of that.  This asymmetry between terms
and labels is necessary because macro expansions might duplicate some
terms.

With each label can be associated a number of snips containing textual
information.  Each snip has a user-defined type that determines the
color of the snip.  A label can have several snips for a given type,
and snips of different types associated with it.  Mouse menus are
available to show or hide all snips of a given type for all labels
corresponding to a given term.  The snips are inserted on the left of
the corresponding term, in an order that is determined by the user
(see the snip-types-and-colors argument of the
init-snips-and-arrows-gui function below for details).

The library defines two mixins that augment the text editors with
various methods, and two functions that are used to initialize the
library.  Both functions return one function to register labels with
the library and one function to change terms in the editors.  A term
in an editor will be recognized and colored by the library only if at
least one of its corresponding labels has been registered with the
library.

All terms corresponding to registered labels must, either directly or
indirectly through embedded sub-editors, be contained in a single
editor called hereafter the top editor.  In the case of the DrScheme
definitions window and its embedded sub-editors (if any), the top
editor will be the definitions window itself.


Usage
=====

(require (lib "snips-and-arrows.ss" "mrflow"))

This will import two mixins:

extend-all-editors-mixin and
extend-top-editor-mixin

and two functions:

init-snips-and-arrows-gui and
init-snips-and-arrows-gui-for-syntax-objects

See the example at the end of this document for a quick how-to.


All text% classes that will be used to create editors that will
contain, either directly or indirectly through embedded editors, terms
to be colored by the library, must be extended using the
_extend-all-editors-mixin_ mixin.  In the case of the DrScheme
definitions window and its embedded sub-editors (if any) this is
simply done by giving this mixin as argument to the
drscheme:unit:add-to-program-editor-mixin function during phase1 of
DrScheme.

The text% class that will be used to create the top editor that will
contain, either directly or indirectly through embedded sub-editors,
all the terms to be colored must be extended using the
_extend-top-editor-mixin_ mixin.  This must be done even if all the
terms to be colored are contained in embedded sub-editors and the top
editor doesn't itself directly contain any terms to be colored. The
mixin extends that text% class with two methods of no arguments:
- _color-registered-labels_, to be called once all labels have been
  registered (see below) in order to color the corresponding terms, to
  start the automatic display of arrows as well as to make the snips
  and arrows related mouse menus available to the user.
- _remove-all-snips-and-arrows-and-colors_, to be called to terminate
  the automatic display of arrows, make the snips and arrows related
  mouse menus unavailable to the user, remove all inserted snips, and
  optionally clear all colors (see below the last argument to
  init-snips-and-arrows-gui and the last optional argument to
  init-snips-and-arrows-gui-for-syntax-objects for details about this
  last point).
In the case of the DrScheme definitions window, using this mixin is
simply done by giving it as argument to the
drscheme:get/extend:extend-definitions-text function.


The init-snips-and-arrows-gui and
init-snips-and-arrows-gui-for-syntax-objects functions are used to
initialize the library.  Exactly one of them must be called before
trying to use the library.  The
init-snips-and-arrows-gui-for-syntax-objects function is a simplified
version of init-snips-and-arrows-gui.

The _init-snips-and-arrows-gui_ function has eleven arguments in the
following order (with a mnemonic name and a type between parenthesis):
1)  the top editor (top-editor: text%), the editor that, either
    directly or indirectly through embedded sub-editors, contains all
    terms that are to be colored by the library.
2)  a function from label to editor (get-editor-from-label: label ->
    text%) returning the editor that directly contains the term
    corresponding to the label.  This function must return the same
    editor for all the labels corresponding to a given term.
3)  a function from label to term position
    (get-mzscheme-position-from-label: label ->
    non-negative-exact-integer) returning the MzScheme position of the
    term corresponding to the label in the editor that directly
    contains it (the editor that would be returned by the
    get-editor-from-label function described just above).  This
    function must return the same position for all the labels
    corresponding to a given term.
4)  a function from label to term color span (get-span-from-label:
    label -> non-negative-exact-integer) indicating the number of
    characters of the term corresponding to the label that should be
    colored.  Coloring of a term starts with the leftmost character of
    the term and continues towards the right for that number of
    characters.  This applies to all terms for which at least one
    label has been registered with the library, regardless of whether
    they are atomic or not, so care must be exercised when computing
    the span for a term containing sub-terms that also have to be
    colored.  Arrows starting or ending at a given term will be
    anchored at half the span of the term, so again care must be taken
    when specifying the span of non-atomic terms.  This function must
    return the same span for all labels corresponding to a given term.
5)  a function from label to a list of arrows data
    (get-arrows-from-label: label -> (listof (list label label
    string))) indicating what arrows should be drawn when the mouse
    pointer is placed over the term corresponding to the label.  Each
    sublist in the list represents one arrow, and must contain, in
    that order, a label corresponding to the starting term for the
    arrow, a label corresponding to the ending term for the arrow, and
    a string representing the arrow color (see color-database<%> in
    the help-desk for a list of color names).
6)  a function from label to style delta (get-style-delta-from-label:
    label -> style-delta%) indicating how the term corresponding to
    the label should be colored.  This function must return the same
    style delta for all the labels corresponding to a given term.
7)  a function from popup menu and a list of labels to void
    (extend-menu-for-labels: popup-menu% (listof label) -> void) that
    can be used to add menu-item% objects to the menu that pops up
    when the user clicks with the right mouse button on a term.  All
    the labels in the list correspond to that term (there might be
    several labels in the list because of macro expansions).  The
    callbacks for the added menu items must not directly modify the
    content of any editor that directly contains colored terms but
    must instead use a function like the first one returned by the
    call to init-snips-and-arrows-gui (see below for details).
8)  a function from snip type (see snip-types-and-colors below) and
    action symbol to string (get-menu-text-from-snip-type: symbol
    symbol -> string) that takes as input a snip type represented as a
    symbol as well as one of the two symbols 'show or 'hide
    representing a user command and returns a string that will be used
    as the text for the menu entry that will allow the user to perform
    said command for the snips of that given type.
9)  a function from snip type and label to a list of strings
    (get-snip-text-from-snip-type-and-label: symbol label -> (listof
    string)) that returns the content of all the snips of the given
    type that will be inserted next to the term corresponding to the
    label when the user uses the snip-related mouse menu entries
    described just above.
10) a list of snip types and colors (snip-types-and-colors: (listof
    (cons symbol string))) that describes all possible types of snips
    and their associated color.  Each sub-list in the list must
    contain two elements: a snip type name represented as a symbol,
    and a corresponding snip color (see color-database<%> in the
    help-desk for a list of color names).  For a given term, snips of
    different types will be inserted from left to right on the left of
    the corresponding term in the order in which their types appear in
    this list.
11) a boolean (clear-colors-immediately?: (union #t #f)) that
    indicates whether, once the user starts modifying the content of
    the editors, the terms colored by the library should be uncolored
    immediately or should be uncolored only the next time a DrScheme
    tool is run or the program executed.
The init-snips-and-arrows-gui then returns two values: a function to
change terms and a function to register a label.  The function to
change terms has type ((listof (cons label string)) -> void).  Each
pair in the function's argument consists of a label representing a
term to be changed and a string that will be used to replace that
term.  It is only necessary to give one label per term to be changed.
It is an error to give two pairs with labels representing the same
term but with different strings.  The function to register a label has
type (label -> void) and is used to indicate to the library that the
corresponding term has to be colored.  The same label can safely be
registered several times.  Unspeakable things involving nasty crawling
bugs will happen to you, and your descendants will be cursed to the
seventh generation if you dare to call this function after having
already called the color-registered-labels method described above.

The init-snips-and-arrows-gui-for-syntax-objects function is a
simplified (yes, really) version of init-snips-and-arrows-gui that
assumes labels are in fact MzScheme syntax objects, and also provides
default values for all the menu and snip related functions that simply
assume that no snips are used at all.  Also, the terms represented by
the syntax objects are assumed to be atomic, meaning they will be
colored in whole and arrows will be anchored at half the terms' spans.
Seemingly unanchored arrows and overlapping colors may result if you
try to use this function with non-atomic terms (i.e. terms containing
sub-terms).  The function has only three required arguments, and five
optional arguments.  The three required arguments are, in that order,
using the mnemonic names from the description of
init-snips-and-arrows-gui above, and with updated types in
parenthesis:
- top-editor (text%)
- get-arrows-from-label (syntax-object -> (listof (list syntax-object
  syntax-object string)))
- get-style-delta-from-label (syntax-object -> style-delta%)
The five optional arguments are as follows, in that order, with their
default value in brackets:
- extend-menu-for-labels (popup-menu% (listof syntax-object) -> void)
  [(lambda (m l) (void)))]
- get-menu-text-from-snip-type (symbol symbol -> string) [internal
  error function]
- get-snip-text-from-snip-type-and-label (symbol syntax-object ->
  (listof string)) [internal error function]
- snip-types-and-colors (listof (cons symbol string)) ['()]
- clear-colors-immediately? (union #t #f) [#f]
The init-snips-and-arrows-gui-for-syntax-objects returns the same two
functions as the init-snips-and-arrows-gui function.


Example
=======

This example assumes that this library is used by a DrScheme tool.
The skeleton for the tool would then look like this:

(module my-tool
  ...
  (require (lib "snips-and-arrows.ss" "mrflow"))
  ...
  (define tool@
    (unit/sig drscheme:tool-exports^
      (import drscheme:tool^)
      ...
      (define (phase1)
        ...
        (drscheme:unit:add-to-program-editor-mixin extend-all-editors-mixin)
        ...)
      ...
      (drscheme:get/extend:extend-definitions-text extend-top-editor-mixin)
      ...
      (drscheme:get/extend:extend-unit-frame
       (lambda (super%)
         (class super%
           (inherit get-definitions-text)
           (rename [super-clear-annotations clear-annotations])
           ; -> void
           (define/override (clear-annotations)
             ...
             (super-clear-annotations)
             (send (get-definitions-text) remove-all-snips-and-arrows-and-colors)
             ...)
           ...
           (define my-tool-button
             (instantiate button%
               ...
               (callback
                (lambda (button event)
                  ...
                  (letrec-values ([(user-change-terms register-label-with-gui)
                                   ; use init-snips-and-arrows-gui-for-syntax-objects
                                   ; if you only deal with syntax objects
                                   (init-snips-and-arrows-gui
                                    (get-definitions-text)
                                    ...
                                    ; for extend-menu-for-labels, if necessary -
                                    ; basically a callback to library-user code
                                    (lambda (popup-menu labels)
                                      ...
                                      (make-object menu-item%
                                        "change stuff"
                                        popup-menu
                                        ; menu callback
                                        (lambda (item event)
                                          ...
                                          (let ([label-and-new-term-pairs
                                                 (my-tool-get-stuff-to-change labels)])
                                            ...
                                            ; callback to the library from within callback to
                                            ; library-user code
                                            (user-change-terms label-and-new-term-pairs)
                                            ...)
                                          ...)
                                        ...)
                                      ...)
                                    ...)])
                    ...
                    ; call to super's method to clean other tools' annotations
                    (super-clear-annotations)
                    ...
                    (drscheme:eval:expand-program
                     ...
                     (lambda (syntax-object-or-eof iter)
                       (if (eof-object? syntax-object-or-eof)
                           (begin
                             ...
                             (send definitions-text color-registered-labels)
                             ...)
                           (begin
                             ...
                             (my-tool-process-syntax-object ... register-label-with-gui ...)
                             ...
                             (iter)))))))))))))))
  ...)
