TextEditor 代码编辑器
写代码没有高亮和光标,就像吃泡面没有调料包——能凑合,但很难受。TextEditor 是一个自带 C++ 语法高亮、虚拟滚动、单光标编辑、IME 中文输入的代码编辑控件。几行代码,你就能在 EasyX 窗口里拥有一个像模像样的 mini IDE。
函数原型
namespace HX {
enum class EditorTokenType {
Unknown,
Keyword,
String,
Comment,
Number,
Identifier,
Operator,
Whitespace
};
struct EditorToken {
HXString Text;
EditorTokenType Type = EditorTokenType::Unknown;
};
struct TextEditorProfile {
HXPoint Size = {600, 400};
int LineHeight = 22;
int CharWidth = 0; // 0 = 自动测量
int FontSize = 20;
int PaddingX = 10;
int ScrollbarSize = 12;
bool ShowLineNumbers = true;
int LineNumberWidth = 50;
bool ReadOnly = false;
int JumpToLine = -1;
int JumpToColumn = 0;
int BlinkIntervalMs = 530;
HXColor LineNumberColor {99, 116, 139, 255};
HXColor GutterBackground {13, 17, 23, 255};
HXColor CurrentLineBackground {24, 31, 42, 255};
std::function<std::vector<EditorToken>(const HXString &)> Lexer;
std::function<HXColor(EditorTokenType)> TokenColorCallback;
};
void TextEditor(const HXString &id, std::vector<HXString> &lines, TextEditorProfile &profile);
}
TextEditor 对 id 有严格要求:必须是固定不变的字符串字面量。如果你把 id 搞成每帧变化的值(比如循环索引、动态拼接的字符串),控件内部会为它创建全新的状态对象,导致光标闪烁异常、双击选词失效、滚动位置丢失。切记:static const 的字面量最安全!
参数详解
TextEditorProfile 配置
| 成员 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Size | HXPoint | {600, 400} | 编辑器宽高 |
LineHeight | int | 22 | 每行像素高度 |
CharWidth | int | 0 | 字符平均宽度,0 表示自动测量 |
FontSize | int | 20 | 字体大小 |
PaddingX | int | 10 | 文本区左侧内边距 |
ScrollbarSize | int | 12 | 滚动条粗细 |
ShowLineNumbers | bool | true | 是否显示左侧行号栏 |
LineNumberWidth | int | 50 | 行号栏宽度 |
ReadOnly | bool | false | 只读模式 |
JumpToLine | int | -1 | 跳转目标行(设置后自动滚动并清零) |
JumpToColumn | int | 0 | 跳转目标列 |
BlinkIntervalMs | int | 530 | 光标闪烁间隔 |
LineNumberColor | HXColor | {99,116,139,255} | 行号文字颜色 |
GutterBackground | HXColor | {13,17,23,255} | 行号栏背景色 |
CurrentLineBackground | HXColor | {24,31,42,255} | 当前行高亮背景 |
Lexer | std::function | 空 | 自定义词法分析器,空则用内置 C++ Lexer |
TokenColorCallback | std::function | 空 | 自定义 Token 颜色映射 |
HXColor 的四个分量是 R, G, B, A,取值范围 0~255。上面的默认配色是一套偏暗色的编辑器风格,和 ModernDark 主题很搭。
返回值
TextEditor 无返回值。所有编辑结果都实时写回 lines 向量,光标、滚动、选中等状态由控件内部通过 HXStatePool 管理。
核心特性
虚拟滚动(仅渲染可见行)
代码再长也不怕。TextEditor 内部会计算当前滚动偏移,只把视口内的那十几行画出来。一万行和一百行,帧率差别不大。
static std::vector<HXString> lines = {
HXStr("#include <iostream>"),
HXStr(""),
HXStr("int main() {"),
HXStr(" std::cout << \"Hello, HiEasyX!\" << std::endl;"),
HXStr(" return 0;"),
HXStr("}"),
};
C++ 语法高亮
默认内置的 C++ Lexer 支持:
- 80+ 关键字:
int,return,class,namespace,constexpr… - 字符串:双引号
"..." - 注释:
//单行、/* */多行 - 数字:十进制、十六进制
0x、二进制0b、八进制、浮点数 - 标识符、运算符(包括多字符运算符
<<->::等)
static HX::TextEditorProfile ep;
// 留空 Lexer,自动使用内置 C++ 高亮
ep.Lexer = {}; // 或干脆不赋值
单光标与基础编辑
| 按键 | 行为 |
|---|---|
← → ↑ ↓ | 移动光标 |
Home / End | 跳到行首 / 行尾 |
Backspace | 删除前一个字符 |
Delete | 删除后一个字符 |
Enter | 换行,自动拆分行 |
Tab | 插入制表符(或缩进) |
Ctrl+A | 全选 |
Ctrl+C / Ctrl+V | 复制 / 粘贴 |
光标有平滑的闪烁效果,由 BlinkIntervalMs 控制。如果你觉得 530ms 闪得太慢,可以改成 300ms。
鼠标交互
- 单击:放置光标
- 拖拽:文本选择
- 滚轮:垂直滚动,光标会自动滚动进可视区域
- 双击:选词(依赖稳定
id,否则失效)
IME 中文输入支持
TextEditor 完整支持输入法:
WM_IME_STARTCOMPOSITION弹出候选框- composition 字符串在光标处实时显示,不会覆盖后续文字
- 候选框位置跟随光标
- 按
Esc或空格/回车确认后,正式写入lines
IME 支持不需要你做任何配置,只要操作系统输入法正常,TextEditor 就能直接打中文、日文、韩文。这是框架底层帮你处理好的。
可插拔 Lexer
不喜欢默认的 C++ 高亮?自己写一个 Lexer 回调!
ep.Lexer = [](const HXString& line) -> std::vector<HX::EditorToken> {
std::vector<HX::EditorToken> tokens;
// 你自己的词法分析逻辑...
// 返回每个 token 的文本和类型
return tokens;
};
Token 颜色回调
even Lexer 也用默认的,但想换个配色主题?TokenColorCallback 让你逐个类型定义颜色。
ep.TokenColorCallback = [](HX::EditorTokenType type) -> HX::HXColor {
switch (type) {
case HX::EditorTokenType::Keyword: return {255, 121, 198, 255}; // 粉红
case HX::EditorTokenType::String: return {241, 250, 140, 255}; // 淡黄
case HX::EditorTokenType::Comment: return {98, 114, 164, 255}; // 灰紫
case HX::EditorTokenType::Number: return {189, 147, 249, 255}; // 淡紫
default: return {248, 248, 242, 255}; // 默认白
}
};
完整示例
下面是一个带自定义配色、跳转行功能、只读切换的代码编辑器演示。
#include <include/hex.h>
#include <include/impl/EasyX/hex_impl_easyx.h>
#include <vector>
int main() {
initgraph(1024, 768);
setbkcolor(WHITE);
cleardevice();
HX::HXInitForEasyX();
HX::SetBuffer(GetWorkingImage());
BeginBatchDraw();
static std::vector<HXString> code = {
HXStr("#include <iostream>"),
HXStr("#include <vector>"),
HXStr(""),
HXStr("// 计算阶乘"),
HXStr("int factorial(int n) {"),
HXStr(" if (n <= 1) return 1;"),
HXStr(" return n * factorial(n - 1);"),
HXStr("}"),
HXStr(""),
HXStr("int main() {"),
HXStr(" std::vector<int> data = {1, 2, 3, 4, 5};"),
HXStr(" for (int x : data) {"),
HXStr(" std::cout << factorial(x) << std::endl;"),
HXStr(" }"),
HXStr(" return 0;"),
HXStr("}"),
};
static HX::TextEditorProfile ep;
ep.Size = {900, 600};
ep.FontSize = 18;
ep.LineHeight = 26;
ep.ShowLineNumbers = true;
// 自定义 Dracula 风格配色
ep.TokenColorCallback = [](HX::EditorTokenType t) -> HX::HXColor {
switch (t) {
case HX::EditorTokenType::Keyword: return {255, 121, 198, 255};
case HX::EditorTokenType::String: return {241, 250, 140, 255};
case HX::EditorTokenType::Comment: return {98, 114, 164, 255};
case HX::EditorTokenType::Number: return {189, 147, 249, 255};
case HX::EditorTokenType::Operator: return {139, 233, 253, 255};
default: return {248, 248, 242, 255};
}
};
static bool readOnly = false;
ExMessage msg;
while (true) {
HX::HXBegin();
while (peekmessage(&msg)) {
HX::PushMessage(HX::GetHXMessage(&msg));
}
HX::Window(HXStr("Code Editor Demo"), HX::WindowProfile());
HX::Text(HXStr("Mini Code Editor — Powered by HiEasyX"));
// 只读切换
HX::CheckboxProfile cp;
cp.Checked = readOnly;
HX::Checkbox(HXStr("Read Only"), cp);
readOnly = cp.Checked;
ep.ReadOnly = readOnly;
// 跳转到第 5 行(factorial 函数)
if (HX::Button(HXStr("Jump to factorial()"), HX::ButtonProfile())) {
ep.JumpToLine = 4; // 0-based
ep.JumpToColumn = 0;
}
// 关键:id 必须是固定字面量!
HX::TextEditor(HXStr("main_editor"), code, ep);
HX::End();
HX::Render();
FlushBatchDraw();
Sleep(16);
}
closegraph();
return 0;
}
再强调一遍:HXStr("main_editor") 这个 id 必须每帧完全相同。如果你写成 HXStr("editor_") + std::to_wstring(someCounter),所有需要跨帧记忆的功能(双击选词、光标闪烁、滚轮速度)都会报废。框架用 id 当键值存状态,键变了就等于换了个全新编辑器。
常见问题
Q: 我想做多光标 / 多选区,支持吗?
A: 当前版本是单光标。多光标、多选区是未来的增强方向,目前建议用 Ctrl+C / Ctrl+V 配合外部编辑器完成复杂批量编辑。
Q: 能支持代码折叠吗?
A: 目前不支持。如果你的文件非常大,建议先用 JumpToLine 做快速定位,或者在外部把代码拆成多个 TextEditor 标签页。
Q: CharWidth 到底要不要手动设置?
A: 留 0 就好。框架会在第一次渲染时自动测量当前字体的平均字符宽度,用于光标定位和水平滚动计算。除非你用了等宽字体且知道精确像素宽度,否则不建议硬编码。
Q: 怎么获取当前光标所在的行号和列号?
A: 当前版本 TextEditorProfile 没有直接暴露光标坐标输出。如果你需要这个信息,可以在 TextEditor 之后立刻读取内部状态(通过 HXStatePool 以 "main_editor" 为键查找 EditorState),但这属于较底层的操作,后续版本可能会增加官方输出字段。
Q: 行号栏的背景色能改吗?
A: 能。修改 GutterBackground 和 LineNumberColor 即可。CurrentLineBackground 控制当前行整行的背景高亮,让你一眼看到光标在哪一行。
运行效果
截图占位符:请补充 $name 的运行效果截图。
截图占位符:请补充
代码编辑器 TextEditor 运行效果的运行效果截图,保存为./assets/代码编辑器 TextEditor_view.png。`n