在 RTerm 中对多字节字符以及非欧洲语言的支持得到改进,RTerm 是 Windows 上 R 的基于控制台的前端。现在可以编辑文本,包括当前区域设置支持的多字节和多宽度字符,以便例如日本的 R 用户可以编辑日语文本。将在 R 4.1 中出现。
这是在 UTF-8 中运行时修复 RTerm 以支持所有 Unicode 字符的副产品,这在 R-devel 的实验性 UCRT 构建中已经成为可能。
有兴趣使用 RTerm 和非欧洲语言的用户受邀测试新版本并报告错误。
测试
要在 R-devel 中的官方 CRAN MSVCRT 构建中试验新的 RTerm,请从此处或按照 4.1 发布流程从通常的地方安装 R。
要试验 UCRT 构建,请参阅博客文章,了解这些构建,并从此处安装 R。
UCRT 构建需要 UCRT,必须在 Windows 7 和 Windows 8 上手动安装,但已随 Windows 10 一起提供。只有在最近的 Windows 10(2019 年 11 月发布或更新版本)上,它才会使用 UTF-8 作为本机编码。在较旧的 Windows 10 上,它将像 MSVCRT 构建一样使用系统区域设置。
当 UTF-8 用作本机编码时,需要在运行 R 之前运行 chcp 65001
(更多内容见下文)。使用 l10n_info()
查看 UTF-8 是否为本机编码。
要在使用欧洲语言区域设置的计算机上测试 MSVCRT 构建,需要切换区域设置以测试多字节字符。这是一个系统范围的设置,只应谨慎更改。可以从提升的 PowerShell 命令行将显示语言切换回英语(当从用户界面变得太困难时)
Set-WinSystemLocale en-US
Set-WinUserLanguageList en-US -Force
新更改已在 Windows 10、8 和 7 中进行测试。
控制台和终端
RTerm 使用(旧版)Windows 控制台 API,最好在 cmd.exe
中进行测试。它预计在 cmd.exe
和 PowerShell 中效果最佳。
在使用 UTF-8 作为本机编码时,需要在运行 RTerm 之前,在这些终端中手动将终端代码页更改为 UTF-8(chcp 65001
)。还需要选择具有适当字形的字体(例如,亚洲语言的 NSimFun
)。
可以通过 Alt+ +hexcode
输入 Unicode 字符。例如,要输入 \u30f3
(片假名字母 N),请按住 Alt,同时按住它,按小键盘上的 +
,键入 30f3
,然后松开 Alt。还可以从其他应用程序简单地复制/粘贴字符进行测试。
RTerm 在自定义/重定向终端(例如 Msys2 中的默认终端 mintty)中的工作效果较差。这与旧版 Windows 控制台设计的局限性密切相关,如这篇 博客文章系列 中详细描述的那样。
旧版设计实际上不允许实现自定义终端。只有当自定义终端使用隐藏控制台缓冲区并观察(检测)在其上发生的更改时,才可以通过黑客手段实现。在输出时,这些终端必须将控制字符转换为控制台 API 函数调用。Mintty 不会直接执行后者,但可以使用 winpty 工具来实现。为了在 mintty 中工作,RTerm 在可用时会自动调用 winpty。Windows 设计中的类似问题也存在于其他自定义终端应用程序中。
在 mintty 中观察到的 RTerm 的一个问题,在新的多字节支持之前就已经存在,通常发生在快速键入时:mintty 失去对 RTerm 的控制,就像它崩溃并退出一样,但 RTerm 仍在后台运行。有时,在快速键入或通过 OpenSSH 粘贴大量文本时,会丢失一些字符,导致行混乱。在 cmd.exe
中未观察到这些问题。
最近,Windows 引入了 conPTY 接口,它最终可以在字节流中使用 ANSI 转义序列作为控制字符,例如在 Unix 上。一旦 winpty/mintty 和其他自定义终端被重写以支持此新 API,RTerm 的这些问题有望消失,甚至可能在 RTerm 不直接支持 conPTY 的情况下,因为 Windows 会进行转换。RTerm 似乎在使用 conPTY 的 Windows 终端中工作良好。
即使使用旧版 Windows 控制台 API,RTerm 也需要处理令人惊讶/未指定的行为(如何接收多字节字符,如何接收代理对,如何接收当前键盘不支持的某些字符等)。其中一些事情往往会发生变化。修复这些问题通常非常简单,但报告错误对于解决这些问题至关重要。
Getline 更改
RTerm 使用 getline 库的定制版本进行行编辑,该库最初由 Chris Thewalt 编写。它对终端的要求极少,因此多年来已移植到许多系统上。从控制字符来看,它仅使用 \b
向左移动一个位置,使用 \r
返回到最左侧位置,使用 \n
向下滚动一行。
Getline 是围绕一个缓冲区设计的,该缓冲区保存行(文本的字节)和缓冲区中的当前光标位置。编辑命令(如插入、删除或光标移动)修改缓冲区,然后触发“修复”操作,该操作更新终端以反映缓冲区中的内容并将光标移动到所需位置。作为一种优化,修复程序知道已更改的最小字节索引,有时也知道最大索引。
Getline 在单行内实现滚动。过长的文本可能会超出右边缘(屏幕右端显示美元符号)和/或超出左边缘。因此,在 getline 中,已编辑的行始终只在屏幕上占据一行,这与例如 readline 中的情况不同。
修复程序使用 \b
将光标向左移动到需要更改的第一个位置,然后打印表示更改的字符,然后打印任何填充(空格)以删除前一行的残余,然后使用 \b
(向左)或发出更多字符(向右)将光标移动到其最终位置,但这些字符已经显示在屏幕上。
该库是在单一可打印字节为单个字符并占据单个屏幕位置的时候实现的。因此,现在已对其进行重写,以反映字符可能是多字节的、可能具有不同的宽度,并且编辑单元可能由多个字符组成。这需要对修复操作进行重大更改。为了维护屏幕位置、字节和编辑单元之间的映射,getline 现在有三个额外的映射(数组)在修复期间更新。
滚动的其中一个后果是屏幕上的第一个字节(超出左侧时大于零)需要与编辑单元对齐,因此其偏移量取决于缓冲区中的文本内容。类似地,超出右侧时的文本宽度不是常量,而是取决于可以适应屏幕的文本中完整编辑单元的宽度。编辑操作需要更改完整的编辑单元并将光标推进到与编辑单元对齐的位置。
字符序列
通常,编辑单元由一个字符组成,在 R 4.1 的 CRAN 构建中始终如此。每个单元多个字符仅在使用 UTF-8 作为本机编码时才相关,因此在 R-devel 的实验性 UCRT 构建中也是如此。
一个 Unicode 序列示例为 \u63\u30c
,字母 c
后跟一个标记,指示终端在字母上放置一个抑扬符。相同的字母可以用 \u10d
表示,但并非所有序列都有紧凑形式。
在 UTF-8 中作为本机语言环境运行时,RTerm 在输入时将任何零宽字符与前一个编辑单元连接起来。这允许输入许多 Unicode 序列,包括 \u63\u30c
,但并非全部,某些序列可能会连接多个正宽字符,例如 \udc1\udca\u200d\udbb
或 \U1f43b\u200d\u2744
。
鉴于修复操作已针对序列实现,因此为这些更复杂的序列添加支持应该不难,但目前似乎无法测试/调试即使是更简单的序列,因为 Windows 上当前可用的终端应用程序似乎都不支持它们。R 本身也不以特殊方式处理序列,因此某些函数会提供令人惊讶的结果。
补充字符
RTerm 现在支持补充 Unicode 字符,这些字符由 UTF-16 中的代理对表示。实际上,这仅与 R-devel 的实验性 UCRT 构建相关,在该构建中,实际上可以由语言环境 (UTF-8) 支持此类字符。
由于支持补充字符,因此当终端支持时,可以使用表情符号字符输入/粘贴和编辑文本。已在 Windows 终端中使用适当的字体进行了测试并成功。它还可以在通过 ssh 在 Linux 上运行的终端中工作。
RTerm 可能是在 R 中修复以支持补充字符的最困难的地方之一,但不是唯一的地方。在撰写本文时,即使可以使用 RTerm 编辑包含表情符号字符的行,也无法使用 print()
打印它们。在 R 在 Windows 上使用 UTF-8 作为本机编码之前,应解决剩余问题。
与字符序列不同,此限制特定于 Windows 的多字节支持设计,其中一些 POSIX 编码转换函数不支持补充字符。在 Linux 或 macOS 上没有问题,因为它们本机支持 UTF-8。