跳到主要内容

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

}
危险

TextEditorid严格要求:必须是固定不变的字符串字面量。如果你把 id 搞成每帧变化的值(比如循环索引、动态拼接的字符串),控件内部会为它创建全新的状态对象,导致光标闪烁异常、双击选词失效、滚动位置丢失。切记:static const 的字面量最安全!

参数详解

TextEditorProfile 配置

成员类型默认值说明
SizeHXPoint{600, 400}编辑器宽高
LineHeightint22每行像素高度
CharWidthint0字符平均宽度,0 表示自动测量
FontSizeint20字体大小
PaddingXint10文本区左侧内边距
ScrollbarSizeint12滚动条粗细
ShowLineNumbersbooltrue是否显示左侧行号栏
LineNumberWidthint50行号栏宽度
ReadOnlyboolfalse只读模式
JumpToLineint-1跳转目标行(设置后自动滚动并清零)
JumpToColumnint0跳转目标列
BlinkIntervalMsint530光标闪烁间隔
LineNumberColorHXColor{99,116,139,255}行号文字颜色
GutterBackgroundHXColor{13,17,23,255}行号栏背景色
CurrentLineBackgroundHXColor{24,31,42,255}当前行高亮背景
Lexerstd::function自定义词法分析器,空则用内置 C++ Lexer
TokenColorCallbackstd::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: 能。修改 GutterBackgroundLineNumberColor 即可。CurrentLineBackground 控制当前行整行的背景高亮,让你一眼看到光标在哪一行。

运行效果

截图占位符:请补充 $name 的运行效果截图。

截图占位符:请补充 代码编辑器 TextEditor 运行效果 的运行效果截图,保存为 ./assets/代码编辑器 TextEditor_view.png。`n