How to serialize/deserialize text editors and structures including text editors

I'm trying to develop a program to manipulate and edit a zipper whose nodes are text editors (objects in the text% class). More precisely, a node is an instance of a class derived from text%, which can include additional information (name of the node, etc.) and methods for changing its state. The interface contains two elements: a canvas for viewing the zipper and manipulating it (moving around in the zipper, changing the name of the node in focus, deleting it, adding a new one, moving the sub-tree in focus, etc.) and another canvas associated with the editor of the node in focus, enabling you to edit its contents. This program works, but I can't manage to save the zipper in a file so that I can open it later in a new editing session. I have a simple solution that allows this, but only for plain text. Schematically, I proceed as follows (I'm simplifying here by considering a tree rather than a zipper and by assuming that a node is a plain text editor):

; Simple structure for a labeled tree.
(serializable-struct tree (label subtrees)#:transparent #:mutable)

(define (tree-map fun tree)
  (tree (fun (tree-label tree)) (map tree-map (tree-subtree tree))

(define (string->text-editor  string)
   (define editor (new text%))
    (send editor insert string)
    editor)

(define (text-editor->string editor)
  (send editor get-text 0 'eof #t #f)))

; define function for saving data to a rkdt file                     
(define (save-rktd data path)
  (if (serializable? data)
      (with-output-to-file path
        (lambda () (write (serialize data)))
        #:exists 'replace)                       
      (error "Data is not serializable")))
      
; define function for reading data from a rkdt file      
(define (read-rktd path)
  (with-input-from-file path
    (lambda () (deserialize (read)))))

(define (tree-write  tree path)
  (save-rktd (tree-map text-editor->string tree) path)

(define (tree-read path)
  (tree-map string->text-editor  (read-rktd path)))

To solve this problem I concentrated on the problem of serializing and deserializing a text editor and tried the following :

(require racket/serialize)
(require racket/gui/base)
(provide (all-defined-out))
(require (except-in racket/gui/base make-color make-pen))

(define (serialize-text text)
  (define editor-stream-out-bytes-base (new editor-stream-out-bytes-base%))
  (define editor-stream-out (make-object editor-stream-out% editor-stream-out-bytes-base))
  (send text write-to-file editor-stream-out)
  (send editor-stream-out-bytes-base get-bytes))

(define (deserialize-text byte-stream)
  (define editor (new text% [auto-wrap #t]))
  (define editor-stream-in-bytes-base (make-object editor-stream-in-bytes-base% byte-stream))
  (define editor-stream-in (make-object editor-stream-in% editor-stream-in-bytes-base))
  (send editor read-from-file editor-stream-in)
  editor)

(define text-editor-frame%
  (class frame%
    (init-field  (text (new text%  [auto-wrap #t])))
    (define editor  text)
    (super-new [min-height 800] [min-width 500])
    (define CANVAS (new editor-canvas%
                                             [parent this]
                                             [editor editor]
                                             [min-height 800]
                                             [min-width 500]))
    ;; Creation of the menu bar
    (define menu-bar (new menu-bar% [parent this]))
    ;; edit & font menus
    (define menu-edit (new menu% [label "Edit"] [parent menu-bar]))
    (append-editor-operation-menu-items menu-edit #f)
    (define menu-font (new menu% [label "Font"] [parent menu-bar]))    
    (append-editor-font-menu-items menu-font)
    ;; Get a serialization of the current content of the editor
    (define/public (serialize) (serialize-text editor))
    ))

(define (make-a-text-editor-frame title (text (new text% [auto-wrap #t])))
  (let ([FRAME (new text-editor-frame% [label title] [text text])])
    (send FRAME show #t)
    FRAME))

;; TEST
; (define frame (make-a-text-editor-frame "Original"))
;; edit a text in the editor canvas using the keyboard and menus
; (define byte-stream (send frame serialize))
; byte-stream ;; to see how it looks!
; (define new-text (deserialize-text byte-stream)) ;; PROBLEM HERE !
; (define new-frame (make-a-text-editor-frame "Copy" new-text))
;; Both text-editor-frames should display the same content but the copy is empty !

I can't identify the problem. Can any of you point me in the right direction? Thank you

1 Like

Tip:

Note in particular the way to format a code block.

More here:
Formatting posts using markdown, BBCode, and HTML - Using Discourse - Discourse Meta

with my apologies for the poor formatting of my text, and thank you for correcting it and sending me the markdown features

You need to use write-editor-global-header, write-editor-global-footer when serializing and read-editor-global-header, read-editor-global-footer when deserializing. Here is a version of serialize-text / deserialize-text which work in your example:

(define (serialize-text text)
  (define editor-stream-out-bytes-base (new editor-stream-out-bytes-base%))
  (define editor-stream-out (make-object editor-stream-out% editor-stream-out-bytes-base))
  (write-editor-global-header editor-stream-out)
  (send text write-to-file editor-stream-out)
  (write-editor-global-footer editor-stream-out)
  (send editor-stream-out-bytes-base get-bytes))

(define (deserialize-text byte-stream)
  (define editor (new text% [auto-wrap #t]))
  (define editor-stream-in-bytes-base (make-object editor-stream-in-bytes-base% byte-stream))
  (define editor-stream-in (make-object editor-stream-in% editor-stream-in-bytes-base))
  (read-editor-global-header editor-stream-in)
  (send editor read-from-file editor-stream-in)
  (read-editor-global-footer editor-stream-in)
  editor)

Alex.

2 Likes

Is it worth packaging Alex's code for reuse? These seem like the sort of "hard to figure out w/o asking the experts" things it would be nice to have wrapped up with a bow.

1 Like

Very much so. I would certainly have appreciated it if these functions had been available in the Racket reference documentation, especially as, although this is generally very well designed, certain parts, such as paragraphs 5 (Editors) and 6 (Snip and Style Classes) of the Racket Graphical Interface Toolkit, are particularly intimidating for non-experts.

1 Like

Opened! :smiley: Point write-to-file to write-editor-global-header · Issue #5224 · racket/racket · GitHub