racketcode-editor

Basic code editor functionality in Racket


I'm creating a program for a live coding performance, for which I want a basic S-expressions code editor (whose contents I input would be evaled as Racket code in the appropriate syntactical context).

Since DrRacket is itself written in Racket, I expected that recreating the text editing functionality of its code editor would be fairly painless, and that it would be documented, but I've found no guidance. I have the following code so far:

(define frame (new frame% [label "Simple Edit"]
                          [width 800]
                          [height 800]))
(define canvas (new editor-canvas% [parent frame]))
(define text (new text%))
(send canvas set-editor text)
(send frame show #t)

(define menu-bar (new menu-bar% [parent frame]))
(define edit-menu (new menu% [label "Edit"] [parent menu-bar]))
(define execution-menu (new menu% [label "Execution"] [parent menu-bar]))
(new menu-item% [label "Run"]
                [parent execution-menu]
                [callback (λ (mi e) (update (send text get-text)))]
                [shortcut #\R]
                [shortcut-prefix '(cmd)])
(append-editor-operation-menu-items edit-menu #f)

(define delta (make-object style-delta% 'change-size 14))
(send delta set-face "Menlo")
(send text change-style delta)

With this I have set the font and its size to an agreeable one, and copy and paste operations, etc. work. But there's a lot of unexpected behaviors, such as:

I don't want to reinvent the wheel, so I googled hard but to no avail, tried looking into the DrRacket source code (which was too complex for my still limited understanding of the language), etc. There doesn't seem to be a good explanation on using the GUI toolkit itself around either (that isn't just the reference), and what I pasted above took me a good deal of trial-and-error, so I don't look forward to implementing all of this basic text editing stuff by hand.

If anyone has a project source code that exemplifies how to get this done, some package that has it resolved, or some pointers that would get me in the right track, it would be much appreciated!


Solution

  • The functionality for the editing part (not the "run" part) is provided by the racket:text% class from the framework library.

    #lang racket/gui
    (require framework)
    
    (define frame
      (new frame% [label "Simple Editor"] [width 800] [height 800]))
    (define text-editor
      (new racket:text%))
    (define canvas
      (new editor-canvas% [parent frame] [editor text-editor]))
    
    (send frame show #true)
    

    This takes care of syntax highlighting, paren-matching, double-click s-expression, and indentation. Your code in the question is a start at adding the "run" functionality, because the callback function can get the text when it's supposed to be run. So now all you need is a function that can take a piece of text and run it. To do that you can use make-module-evaluator from racket/sandbox.

    (require racket/sandbox)
    
    (define (run-text str)
      (define repl-ev
        (parameterize ([sandbox-output (current-output-port)]
                       [sandbox-error-output (current-error-port)])
          (make-module-evaluator str)))
      (void))
    

    Then you can use run-text in your callback function like this:

                    [callback (λ (mi e) (run-text (send text-editor get-text)))]
    

    The way it's currently set up, running the module prints the results in DrRacket's interactions window. You probably want your own interactions window for that, and I'm not sure how to do that.