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)

有了这个,我将字体及其大小设置为令人满意的字体,复制和粘贴操作等工作。但是有很多意外行为,例如:

  • 按修饰符+字母组合键仍会插入字母,而不是忽略它。
  • 按 alt+left 或 cmd+left(Mac 用户)会将插入符号移动单个字符而不是单词或到边距。
  • 双击不会选择单词。

我不想重新发明轮子,所以我努力谷歌但无济于事,尝试查看 DrRacket 源代码(对于我对语言的理解来说太复杂了)等。关于使用 GUI 工具包本身似乎也没有一个很好的解释(这不仅仅是参考),我上面粘贴的内容花了我大量的试验和错误,所以我不期待手动实现所有这些基本的文本编辑内容。

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

DrRacket大量使用framework库,这是一个建立在racket/gui之上的GUI组件的更高级别的工具包。支持语法突出显示的编辑器组件接口是color:text<%>,它支持相当高级的、完全可自定义的语法突出显示,该接口基于您提供给start-colorer方法的任意词法分析函数。color:text<%>接口本身基于text:basic<%>,它也来自framework并实现了您描述的一些与着色无关的编辑行为。

由于color:text<%>是一个接口,因此不能直接使用,但framework还提供了color:text%,一个可以像任何其他组件一样创建和操作的具体实现。如果您需要更大的灵活性,还有color:text-mixin,它允许向任意文本编辑器类添加color:text<%>功能。text:basic<%>存在text:basic%text:basic-mixin形式的相似之处。

framework的源代码是gui-lib包的一部分,可在 GitHub 上找到 这里.您还可以浏览 DrRacket 中的源代码,而无需克隆任何内容 - 只需右键单击模块名称并选择Open main.rkt或类似名称,或使用">→打开需要路径的文件..."菜单选项并键入已安装模块的路径以打开其源代码。

为了更好地了解如何使用color:text<%>的语法着色功能,查看syntax-color/default-lexer实现所需协议的非常简单的词法分析器或syntax-color/racket-lexerDrRacket实际使用的更复杂的词法分析器以突出显示Racket代码也可能很有用。

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

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

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

这将处理语法突出显示、括号匹配、双击 s 表达式和缩进。问题中的代码是添加"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的交互窗口中打印结果。您可能想要自己的交互窗口,我不确定该怎么做。

@talarmin 不幸的是,您的好建议在格式错误中丢失了。我重做了它以帮助那些有兴趣测试您的示例的人:

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

lang racket/gui

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

(定义 ns (make-base-namespace)) (评估'(需要方案)NS)

(定义框架 (新帧% [标签"简单编辑器"] [宽度 800] [高度 200])) (定义文本编辑器 (新球拍:文本%)) (定义画布 (新编辑器画布% [父框架](最小高度 120)[编辑器文本编辑器]))

(发送帧显示 #true)

(定义文本编辑器2 (新球拍:文本%)) (定义画布2 (新编辑器画布% [父框架] (最小高度 120)[编辑器文本编辑器 2]))

(定义 HPA (新的水平面板% (父框架)(对齐'(中心中心)

)))(定义按钮评估 (新按钮百分比(父 HPA) (标签"估价师") (样式 '(边框)) (回调 (lambda (b e) ; b=按钮, e=事件 (发送文本编辑器2擦除) (let ((L (read-from-string-all (send text-editor-get-text)))) (for-each (lambda (expr) (发送文本编辑器2插入(expr->string (eval expr ns))) (发送文本编辑器2插入"")) L))))

最新更新