[main]Notes on TeXmacs

Implementing previews for link targets

[last revision: 2020.11.9]

Let us see how Joris implemented a preview for link target in r13081. This requires adding new functionalities at several levels.

Preview have to be shown in a popup balloon as soon as the user hovers the associate reference locus with the mouse. So we need to modify the reference and pageref tags. There are “primitive” tags defined in the C++ source src/Typeset/Env/env_default.cpp with

tree ln3 (LINK, "hyperlink", copy (ref_id), copy (dest_ref));
env ("reference") = tree (MACRO, "Id", 
                                 tree (LOCUS, copy (ref_id), 
                                              ln3, reftxt));
env ("pageref") = tree (MACRO, "Id", 
                               tree (LOCUS, copy (ref_id), 
                                            copy (ln3), preftxt));

A new package at packages/utilities/preview-ref.ts containing a redefinition of the reference and pageref tags:

<document|<use-module|(link ref-edit)>||<assign|reference|<macro|Id|<locus|<id|<hard-id|Id>>|<link|hyperlink|<id|<hard-id|Id>>|<url|<merge|#|Id>>>|<link|mouse-over|<id|<hard-id|Id>>|<script|preview-reference|<quote-arg|Id>|Id>>|<get-binding|Id>>>>||<assign|pageref|<macro|Id|<locus|<id|<hard-id|Id>>|<link|hyperlink|<id|<hard-id|Id>>|<url|<merge|#|Id>>>|<link|mouse-over|<id|<hard-id|Id>>|<script|preview-reference|<quote-arg|Id>|Id>>|<get-binding|Id|1>>>>>

Note the use of the use-module tag to indicate that the Scheme module (link ref-edit) must be made available to TeXmacs beforehand in order to be able to call the Scheme procedure preview-reference. The module (link ref-edit), defined in the Scheme source /progs/link/ref-edit.scm, being with

(texmacs-module (link ref-edit)
  (:use (generic generic-edit)
        (generic document-part)
        (text text-drd)))

and contains the definition of preview-reference as a tm-define (i.e. a global symbol visible in all the program). Note the (:secure #t) which indicates to TeXmacs that this producedure does not perform any dangerous operation and therefore can be called without notifying the user.

(tm-define (preview-reference body body*)
  (:secure #t)
  (and-with ref (tree-up body)
    (with (x1 y1 x2 y2) (tree-bounding-rectangle ref)
      (and-let* ((packs (get-style-list))
                 (pre (document-get-preamble (buffer-tree)))
                 (id (and (tree-atomic? body*) 
                          (tree->string body*)))
                 (balloon (ref-preview id))
                 (zf (get-window-zoom-factor))
                 (sf (/ 4.0 zf))
                 (mag (number->string (/ zf 1.5)))
                 (balloon* ‘(with "magnification" ,mag ,balloon))
                 (w (widget-texmacs-output
                     ‘(surround (hide-preamble ,pre) "" ,balloon*)
                     ‘(style (tuple ,@packs)))))
        (show-balloon w x1 (- y1 1280))))))

This procedure invoke show-balloon to display a popup to the user which contains a widget to output TeXmacs documents (built via widget-texmacs-output) after suitably scaling it via a magnification tag. The content itself is produced by the procedure ref-preview :

(tm-define (ref-preview id)
  (and-with l (and-nnull? (search-label (buffer-tree) id))
    (label-preview (car l))))

which in turn calls the helper procedure label-preview (internal to the module):

(define (label-preview t)
  (and-with doc (tree-search-upwards t preview-context?)
    (with math? (tree-search-upwards t math-context?)
      (when (and (tree-up doc) (tree-up (tree-up doc))
                 (tree-is? (tree-up doc) 'document)
                 (preview-expand-context? (tree-up (tree-up doc))))
        (set! doc (tree-up doc)))
      (when (tm-is? doc 'row)
        (set! doc (apply tmconcat (map uncell (tm-children doc)))))
      (set! doc (clean-preview doc))
      (when math?
        (set! doc ‘(with ‘‘math-display'' ‘‘true'' (math ,doc))))
      ‘(preview-balloon ,doc))))

The construction of the preview content is performed by the snippet ‘(preview-balloon ,doc) which evaluates to a TeXmacs s-tree containing a singe preview-balloon tag for the rendering of the preview. This is defined at the macro language level in packages/standard/std-fold.ts as

<assign|preview-balloon|<macro|body|<tabular|<tformat|<cwith|1|1|1|1|cell-hyphen|t>|<cwith|1|1|1|1|cell-background|#edc>|<twith|table-width|40em>|<twith|table-hmode|min>|<table|<row|<cell|<document|body>>>>>>>>

This markup provides a custom style and background color for the preview area, including a minimum width.

The body argument is feed with a meaningful snippet of the source of the link. This is determined by the label-preview procedure by navigating the document tree from the source position (as indicated by the t parameter) towards higher levels of the structure hierarchy via (tree-search-upwards t preview-context?). The predicate preview-context? looks for meaningful context: a row of a table or a leaf of a document tag:

(define (preview-context? t)
  (or (tree-is? t 'row)
      (and (tree-up t) (tree-is? (tree-up t) 'document))))

We want also to include some major markup in the preview, e.g. theorems, etc...

(define (preview-expand-context? t)
  (tree-in? t '(theorem proposition lemma corollary conjecture
                theorem* proposition* lemma* corollary* conjecture*
                definition axiom
                definition* axiom*)))

The procedure clean-preview preforms some recursive cleaning on the source snippet:

(define (clean-preview t)
  (cond ((tm-is? t 'document)
         ‘(document ,@(map clean-preview (tm-children t))))
        ((tm-is? t 'concat)
         (apply tmconcat (map clean-preview (tm-children t))))
        ((tm-in? t (section-tag-list))
         (with l (symbol-append (tm-label t) '*)
           ‘(,l ,@(tm-children t))))
        ((tm-in? t '(label item item* 
                           bibitem bibitem* eq-number)) "")
        ((or (tm-func? t 'equation 1) (tm-func? t 'equation* 1))
         ‘(equation* ,(clean-preview (tm-ref t 0))))
        ((tm-in? t '(eqnarray eqnarray* tformat table row cell))
         ‘(,(tm-label t) ,@(map clean-preview (tm-children t))))
        (else t)))