Racket 中的基本代码编辑器功能

Basic code editor functionality in Racket

我正在创建一个用于实时编码性能的程序,为此我需要一个基本的 S 表达式代码编辑器(我输入的内容将 eval 在适当的语法上下文中编辑为 Racket 代码) .

由于 DrRacket 本身是用 Racket 编写的,我希望重新创建其代码编辑器的文本编辑功能会相当轻松,并且会记录在案,但我没有找到任何指导。到目前为止我有以下代码:

(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)

有了这个我就把字体和大小设置成了一个合适的,复制粘贴等操作都可以了。但是有很多意想不到的行为,例如:

我不想重新发明轮子,所以我用谷歌搜索但无济于事,尝试查看 DrRacket 源代码(对于我对语言的理解仍然有限,它太复杂了)等。没有这似乎不是关于使用 GUI 工具包本身的一个很好的解释(这不仅仅是参考),而且我上面粘贴的内容让我进行了大量的反复试验,所以我不期待手动实现所有这些基本的文本编辑功能。

如果有人有一个项目源代码来举例说明如何完成这项工作,一些解决了它的包,或者一些可以让我走上正轨的指针,我将不胜感激!

DrRacket 大量使用了 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<%>,它也来自 framework 并实现了您描述的一些非着色相关的编辑行为。

由于color:text<%>是一个接口,不能直接使用,但是framework也提供了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.

framework 的源代码是 the gui-lib package, available on GitHub here 的一部分。您还可以在 DrRacket 中浏览源代码而无需克隆任何东西——只需右键单击模块名称并选择 Open main.rkt 或类似的,或使用 File → Open Require Path... 菜单选项并输入已安装模块的路径以打开其源代码。

为了更好地了解如何使用 color:text<%> 的语法着色功能,查看 syntax-color/default-lexer for a very simple lexer that implements the required protocol or syntax-color/racket-lexer DrRacket 实际使用的更复杂的词法分析器可能也很有用高亮球拍代码。

最后,还值得注意的是,所有这些实际上都可以通过使用 #lang 机制 在 DrRacket 本身 中自定义,因此自定义 #langs实际上可以提供他们自己的词法分析器,DrRacket 将使用这些分析器。这显然需要最少的重新发明轮子,但听起来您想完全实现自己的编辑器,在这种情况下,使用 framework 中的组件将是您最好的选择。

编辑部分(不是 "run" 部分)的功能由 framework 库中的 racket:text% class 提供。

#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)

这会处理语法突出显示、paren 匹配、双击 s-expression 和缩进。您在问题中的代码是添加 "run" 功能的开始,因为回调函数可以在应该是 运行 时获取文本。所以现在你所需要的只是一个函数,它可以接受一段文本并 运行 它。为此,您可以使用 racket/sandbox 中的 make-module-evaluator

(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))

然后你可以在你的回调函数中使用 run-text 像这样:

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

按照目前的设置方式,运行启用模块会在 DrRacket 的交互中打印结果 window。为此,您可能需要自己的互动 window,但我不确定该怎么做。

语言racket/gui

(需要框架) (要求(仅在 mzlib/string 从字符串中读取所有 expr->string))

(定义 ns (make-base-namespace)) (eval '(需要方案) ns)

(定义框架 (新框架%[标签"Simple Editor"][宽度800][高度200])) (定义文本编辑器 (新 racket:text%) (定义 canvas (new editor-canvas% [parent frame] (min-height 120)[editor text-editor]))

(发送帧显示#true)

(定义文本编辑器2 (新 racket:text%) (定义 canvas2 (new editor-canvas% [parent frame] (min-height 120)[editor text-editor2]))

(定义hpa(新水平面板% (parent frame)(alignment '(center center))))

(定义按钮评估 (新按钮 %(父 hpa) (标签 "Evaluer") (样式'(边框)) (回调 (lambda (b e) ; b=按钮, e=事件 (发送 text-editor2 擦除) (let ((L (read-from-string-all (send text-editor get-text))))) (for-each (lambda (expr) (发送 text-editor2 insert (expr->string (eval expr ns))) (发送 text-editor2 插入 "\n")) 大号))))))