Reputation: 43
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 eval
ed 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!
Upvotes: 4
Views: 1718
Reputation: 1
@talarmin Unfortunately your good suggestion got lost in bad formatting. I redid it to help those interested to test your example:
#lang racket/gui
(require framework)
(require (only-in mzlib/string read-from-string-all expr->string))
(define ns (make-base-namespace))
(eval '(require scheme) ns)
(define frame (new frame%
[label "Simple Editor"]
[width 800]
[height 200]))
(define text-editor (new racket:text%))
(define canvas (new editor-canvas%
[parent frame]
[min-height 120]
[editor text-editor]))
(send frame show #true)
(define text-editor2 (new racket:text%))
(define canvas2 (new editor-canvas%
[parent frame]
[min-height 120]
[editor text-editor2]))
(define hpa (new horizontal-panel%
[parent frame]
[alignment '(center center)]))
; b=button, e=event
(define BUTTON-EVAL
(new button%
[parent hpa]
[label "Evaluer"]
[style '(border)]
[callback (lambda (b e)
(send text-editor2 erase)
(let ((L (read-from-string-all (send text-editor get-text))))
(for-each (lambda (expr)
(send text-editor2 insert
(expr->string (eval expr ns)))
(send text-editor2 insert "\n")) L)
))]))
Upvotes: 0
Reputation: 9
(require framework) (require (only-in mzlib/string read-from-string-all expr->string))
(define ns (make-base-namespace)) (eval '(require scheme) ns)
(define frame (new frame% [label "Simple Editor"] [width 800] [height 200])) (define text-editor (new racket:text%)) (define canvas (new editor-canvas% [parent frame] (min-height 120)[editor text-editor]))
(send frame show #true)
(define text-editor2 (new racket:text%)) (define canvas2 (new editor-canvas% [parent frame] (min-height 120)[editor text-editor2]))
(define hpa (new horizontal-panel% (parent frame)(alignment '(center center))))
(define BUTTON-EVAL (new button% (parent hpa) (label "Evaluer") (style '(border)) (callback (lambda (b e) ; b=button, e=event (send text-editor2 erase) (let ((L (read-from-string-all (send text-editor get-text)))) (for-each (lambda (expr) (send text-editor2 insert (expr->string (eval expr ns))) (send text-editor2 insert "\n")) L))))))
Upvotes: -1
Reputation: 8373
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.
Upvotes: 2
Reputation: 43902
DrRacket makes heavy use of the framework
library, which is a higher level toolkit of GUI components built on top of racket/gui
. The editor component interface that supports syntax highlighting is color:text<%>
, which supports fairly advanced, completely customizable syntax highlighting based on an arbitrary lexing function you provide to the start-colorer
method. The color:text<%>
interface is itself based on top of text:basic<%>
, which also comes from framework
and implements some of the non-colorization related editing behaviors that you describe.
Since color:text<%>
is an interface, it cannot be used directly, but framework
also provides color:text%
, a concrete implementation that can be created and manipulated like any other component. If you need more flexibility, there’s also color:text-mixin
, which allows adding color:text<%>
functionality to arbitrary text editor classes. There exist parallels for text:basic<%>
in the form of text:basic%
and text:basic-mixin
.
The source code for framework
is a part of the gui-lib
package, available on GitHub here. You can also browse the source code within DrRacket without needing to clone anything—just right click on a module name and choose Open main.rkt or similar, or use the File → Open Require Path... menu option and type the path of a module you have installed to open its source code.
To get a better feel for how to use the syntax colorization functionality of color:text<%>
, it might also be useful to take a look at syntax-color/default-lexer
for a very simple lexer that implements the required protocol or syntax-color/racket-lexer
for a more complex lexer actually used by DrRacket to highlight Racket code.
Finally, it’s also worth noting that all of this can actually be customized within DrRacket itself by using the #lang
mechanism, so custom #lang
s can actually provide their own lexers, which DrRacket will use. That would obviously require the least reinventing the wheel, but it sounds like you want to implement your own editor entirely, in which case using the components from framework
is going to be your best bet.
Upvotes: 3