01 快速入门
本章带你从零开始:准备环境、创建项目、编写第一个易狗程序并编译运行。
1.1 环境准备
易狗IDE 采用完整外置化架构,exe 只做 IDE 逻辑,所有资源以磁盘文件形式存放。你只需准备:
| 依赖 | 版本要求 | 说明 |
|---|---|---|
| Go SDK | 1.25.0+ | 用户程序编译必需;IDE 设置中可配置非 PATH 路径 |
| 操作系统 | Win/macOS/Linux | IDE 本体跨平台桌面应用开发 |
| Delve (dlv) | 可选 | 仅在使用调试器时需要 |
用户程序编译使用 wails-template/vendor/ 离线依赖与 -mod=vendor 标志,无需配置 GOPROXY 即可编译。
1.2 创建项目
启动 IDE 后,在起始页选择「创建项目」。可从内置模板选择:
- blank — 空白项目
- console — 控制台程序
- window — 窗口程序(含启动窗口设计文件
.ew) - golib_demo — Go 库调用示例
1.3 第一个程序
项目创建后,主入口文件为 src/main.eg。打开编辑器编写:
// 我的第一个易狗程序 函数 主函数() { 打印("你好,易狗IDE!") 变量 名字 := "道生易" 打印("欢迎," + 名字) }
1.4 编译运行
点击标题栏中间的编译运行按钮(播放图标,强调色高亮),IDE 会:
- 调用后端转译器,将
.eg转 Go 代码 - 合并
.elib扩展包源码(全局libs/+ 项目libs/) - 处理
@嵌入块,生成//line错误定位指令 - 复制运行时模板到临时目录,写入用户代码
go build编译并运行
编译产物为 egruntime.exe(固定文件名,保留 DWARF 调试信息)。release 模式则生成带版本号产物并可选 UPX 压缩。
1.5 项目结构详解
创建项目后,IDE 会在指定目录生成完整的项目骨架。下表列出每个目录与关键文件的作用:
| 路径 | 类型 | 说明 |
|---|---|---|
src/main.eg | 源码 | 程序主入口文件,包含 主函数(),转译后映射为 Go 的 main.mainImpl |
src/*.ew | 设计文件 | 窗口设计器生成的 JSON IR,描述组件树/属性/事件绑定 |
src/*.eg | 源码 | 与 .ew 同名的代码文件,承载事件处理函数 |
src/module/ | 源码 | 用户自拆分模块,可被 main.eg 引用 |
src/include/ | 源码 | 跨项目共享模块,软链接或拷贝形式引入 |
libs/ | 扩展 | 项目级 .elib 支持库(与全局 libs/ 合并) |
components/ | 扩展 | 项目级组件包,扩展窗口设计器组件箱 |
resources/ | 资源 | 图片、字体、音频等静态资源,编译时按需嵌入 |
lib/ | 原生 | 第三方 DLL、静态库,供 @嵌入 调用 |
go.mod | Go | Go 模块定义,由 IDE 维护,用户一般不需手改 |
wails-template/ | Wails | Wails v3 框架模板与 vendor/ 离线依赖 |
wails-template/vendor/ | Go | 离线依赖目录,配合 -mod=vendor 免 GOPROXY 编译 |
.eg/project.eg.json | 元数据 | 项目配置:名称/版本/调试器/编译选项 |
.eg/layout/*.ir.json | 元数据 | 窗口设计器中间表示(IR) |
.eg/memory/ | 元数据 | AI 项目级记忆库,跨会话保留上下文 |
.eg/cache/ | 元数据 | 编译缓存(转译产物、依赖快照) |
out/ | 输出 | 编译中间产物目录 |
bin/ | 输出 | 最终可执行文件输出目录 |
.ew 与对应 .eg 必须同名同目录,IDE 才能在事件下拉中正确定位源码。例如 window_main.ew ↔ window_main.eg。
1.6 IDE 界面介绍
易狗IDE 主界面采用经典的五区布局,各区职责明确:
| 区域 | 位置 | 主要功能 |
|---|---|---|
| 标题栏 | 顶部 | 三段布局:左侧为应用图标 + 应用名 + 保存/另存为/撤销/重做;中间为编译运行(播放图标,强调色高亮)+ 生成可执行文件 + 调试;右侧为关于/主题切换/代码片段/系统设置/分隔线/最小化/最大化/关闭 |
| 左侧文件树 | 左 1 | 项目文件树(src/libs/components),支持新建/重命名/删除;下方为层级列表(窗口设计器中组件树) |
| 左侧工具面板 | 左 2 | 4 个固定标签 + 插件追加标签(G7 插件面板):文件/项目/支持/AI |
| 中间编辑器 | 中央 | Monaco 编辑器,语言 ID 为 egou;窗口设计器作为同区切换标签 |
| 右侧属性面板 | 右 | 三组属性(基本/外观/事件)+ 事件下拉;底部为事件定位组合框 |
| 底部输出面板 | 底部 | 输出/错误/提示/书签/历史/调试 六标签切换;编译错误可点击跳转(AI 不在输出面板,位于左侧工具面板第 4 个标签) |
| 断点栏 | 编辑器左 | 行号栏(glyph margin):点击切换断点,F9 切换;当前执行行黄色高亮 |
Editor 为单实例复用设计:切换文件不会创建多个 Monaco 实例,而是复用同一编辑器并切换 model。断点、书签、折叠状态按 fileId 持久化到 localStorage,切换回来自动恢复。
1.7 常用快捷键速查表
| 快捷键 | 功能 | 说明 |
|---|---|---|
| Ctrl+Click | 跳转到定义 | 同文件函数/方法 + 跨文件 .elib 函数 |
| F12 | 跳转到定义 | 与 Ctrl+Click 等价,键盘入口 |
| Shift+F12 | 查找引用 | gopls 提供,列出当前符号的所有引用位置 |
| Ctrl+F2 | 切换书签 | 当前行打标/取消 |
| Alt+F2 | 下一个书签 | 向后跳转 |
| Alt+Shift+F2 | 上一个书签 | 向前跳转(F2 留给重命名) |
| F9 | 切换断点 | 调试时使用,Shift+点击也可添加 |
| F5 | 继续执行 | 调试已启动时继续;未启动时启动调试 |
| F10 | 单步步过 | Next,不进入函数 |
| F11 | 单步步入 | Step,进入函数 |
| Shift+F11 | 单步步出 | StepOut,跳出当前函数 |
| Ctrl+S | 手动保存 | 自动保存 debounce 3s,仅对有路径文件生效 |
书签是导航标记(蓝色圆点),断点是调试停点(红色圆点)。两者共用 glyph margin,但功能独立:Ctrl+F2 切换书签,F9 切换断点。
1.8 gopls 语言服务器集成
自 v0.12.31 起,易狗IDE 接入 Go 官方语言服务器 gopls,让编辑器从"语法高亮 + 中文关键字补全"升级为真正的 IDE。v0.12.32 起将 gopls 内置到发布包 bin/tools/gopls/,用户零配置即可使用。
由于 gopls 只识别 .go 文件,后端维护一个影子工作区:把项目中的 .eg 文件按原结构镜像为 .go,gopls 在影子目录上工作,返回的路径自动映射回 .eg。
| LSP 能力 | 触发方式 | 说明 |
|---|---|---|
| 跳转定义 | F12 / Ctrl+Click | 跳转到符号定义处,支持 Go 标准库函数 |
| 查找引用 | Shift+F12 | 列出当前符号的所有引用位置 |
| 悬浮提示 | 鼠标悬停 | 显示类型签名、文档注释 |
| 实时诊断 | 编辑时自动 | 编译错误、vet 问题以红波浪线标注 |
| 格式化 | 格式化命令 | gofmt / goimports 风格 |
| 文档符号 | 大纲面板 | 列出当前文件的函数/类型/变量大纲 |
gopls 提供了对 Go 标准库(fmt / os / strings / strconv 等)的跳转与补全能力。在 @嵌入 块内输入 os. 即可触发标准库函数补全。gopls 未安装或启动失败时不阻断主流程,编辑器仍保留中文关键字补全与 egParser 诊断。
启动 LSP 时按三段回退查找 gopls:用户自定义路径 → 内置 bin/tools/gopls/ → 系统 PATH。可在系统设置 → 编译 → 语言服务器 (gopls) 中查看状态与配置路径。
02 语法规范
易狗IDE 采用 Go 原生语法关键词中文化,仿易语言用法。一切以 Go 语法及格式为准。
2.1 中文关键字
| 中文关键字 | Go 等价 | 说明 |
|---|---|---|
函数 | func | 函数声明 |
变量 | var / := | 变量声明 |
如果 | if | 条件分支(带否则块) |
如果真 | if | 单分支条件,无 else 块,比 如果 更简洁 |
否则 | else | 否则分支 |
判断循环 | for | 条件循环 |
计次循环首 | for | 计数循环 |
变量循环首 | for range | 遍历循环(切片/映射等) |
跳出循环 | break | 跳出当前循环 |
到循环尾 | continue | 跳过本次循环剩余部分,进入下一轮 |
选择 | switch | 多分支 |
情况 | case | 分支项 |
默认 | default | 默认分支 |
返回 | return | 返回 |
结束 | — | 结束当前块 |
2.2 控制结构
如果 / 否则(输入「如果」会自动展开为完整骨架):
如果 (age >= 18) { 打印("已成年") } 否则 { 打印("未成年") }
判断循环(输入「判断循环」自动展开):
判断循环 (i < 10) { 打印(i) i = i + 1 }
选择(switch-case,输入「选择」自动展开 情况 + 默认 骨架):
选择 (day) { 情况 1: 打印("周一") 情况 2: 打印("周二") 默认: 打印("其他") }
2.3 @嵌入 Go 原生混编
使用 @嵌入 与 @结束 包裹 Go 原生代码块,实现中文与 Go 无缝混编:
函数 计算阶乘(n 整数) 整数 { @嵌入 if n <= 1 { return 1 } return n * factorial(n-1) @结束 }
@嵌入 块内的代码原样保留至生成的 Go 文件,需符合 Go 语法。AST 编译器将 @嵌入/@结束 作为独立 Token 处理。
2.4 中文符号自动转换
编辑器自动将中文符号转换为英文等价物:
| 中文符号 | 转换后 |
|---|---|
() | () |
【】 | [] |
"" | "" |
, | , |
。 | . |
2.5 缩进与注释
- 缩进:2 或 4 空格(设置面板用 NSelect 切换,不用 NSlider)
- 注释:Go 风格
// - 单引号:中文代码区禁止单引号字符串;但
@嵌入块内 Go rune 字面量(如'f')是合法的 - 语言 ID:Monaco 编辑器中固定为
egou
2.6 编辑器增强功能
| 功能 | 快捷键 | 说明 |
|---|---|---|
| 跳转到定义 | Ctrl+Click / F12 | 同文件函数/方法 + 跨文件 .elib 函数 |
| 切换书签 | Ctrl+F2 | 下一个 Alt+F2,上一个 Alt+Shift+F2(F2 留给重命名) |
| 切换断点 | F9 | Shift+点击也可添加断点 |
| 自动保存 | — | debounce 3s,仅对有路径的文件,无对话框 |
| 代码折叠 | — | 按 fileId 持久化到 localStorage |
2.7 数据类型
EGOU 采用混合模式:类型既可用 Go 原生写法(int/string/float64/bool…),也可用中文类型别名,二者等价、编译期自动转译。常用别名:整数→int、文本→string、逻辑→bool、小数→float64、字节→byte、字符→rune、结构体→struct、接口→interface、通道→chan。复合类型用 Go 原生括号语法 + 内层中文类型自动替换:[]整数→[]int、map[文本]整数→map[string]int、通道 整数→chan int。数学四则(+ - * /)保持原生 Go。自定义类型仍用 @嵌入 + Go type。
// 自定义类型(@嵌入 Go 原生 type 关键字) @嵌入 type UserID int type Score float64 @结束 函数 主函数() { 变量 uid = 1001 // Go 自动推断为 int 变量 sc = 98.5 // Go 自动推断为 float64 打印(uid, sc) }
// 中文类型别名(与 Go 原生等价,编译期自动转译) 变量 年龄 整数 = 18 变量 分数 小数 = 95.5 变量 已婚 逻辑 = 真 变量 编号 []整数 = []整数{1, 2, 3} 函数 主函数() { 打印(年龄, 分数, 已婚, 编号) }
类型转换需用 @嵌入 调用 Go 转换函数,如 int(f)、string(n)、strconv.Itoa(n) 等。中文语法本身不提供转换关键字。
布尔值可用 @嵌入 的 true/false,或 krnln 支持库的 取常量_真()/取常量_假()。真/假 是否作为中文关键字直接使用,以转译器实际支持为准。
2.8 运算符
| 类别 | 运算符 | 说明 |
|---|---|---|
| 算术 | + - * / % | 加减乘除取模;+ 对文本为拼接 |
| 比较 | > < >= <= == != | 返回布尔值 |
| 逻辑 | 且 或 非 | 仿易语言别名,以转译器实际支持为准。如不支持,可在 @嵌入 块内使用 && || ! |
| 赋值 | := = | := 短声明(首次定义),= 重新赋值 |
| 复合赋值 | += -= *= /= %= | 同 Go,op 后加 = |
| 位运算 | & | ^ << >> | 需用 @嵌入 表达 |
| 取地址/解引用 | & * | 需用 @嵌入 表达 |
变量 a := 10 变量 b := 3 打印(a + b) // 13 打印(a / b) // 3(整数除法) 打印(a % b) // 1 如果 (a > b 且 b != 0) { 打印("条件成立") } a += 5 // a 现在是 15
2.9 数组与切片
数组的声明与遍历需借助 @嵌入,因为中文语法不直接提供 [] 数组字面量语法。最常见做法是用切片 + append:
函数 主函数() { @嵌入 var nums []int = []int{1, 2, 3} nums = append(nums, 4, 5) fmt.Println("len=", len(nums), "cap=", cap(nums)) for i, v range nums { fmt.Printf("nums[%d]=%d\n", i, v) } @结束 }
| 操作 | Go 函数 | 说明 |
|---|---|---|
| 长度 | len(s) | 元素数量 |
| 容量 | cap(s) | 底层数组容量 |
| 追加 | append(s, x) | 返回新切片,需重新赋值 |
| 切片 | s[a:b] | 左闭右开区间 |
| 删除 | append(s[:i], s[i+1:]...) | 需用 @嵌入 |
2.10 函数详解
函数声明使用 函数 关键字,支持多返回值、命名返回值、可变参数、闭包:
// 多返回值 函数 除法(a, b 整数) (整数, 文本) { 如果 (b == 0) { 返回 0, "除数不能为零" } 返回 a / b, "" } // 命名返回值(裸返回) 函数 计算(x 整数) (结果 整数, 错误 文本) { 结果 = x * 2 返回 // 裸返回 } // 可变参数(@嵌入 Go 原生语法) 函数 求和(nums ...整数) 整数 { @嵌入 var sum int for _, n range nums { sum += n } return sum @结束 } // 闭包 函数 计数器() { @嵌入 gen := func() func() int { i := 0 return func() int { i++ return i } }() fmt.Println(gen(), gen(), gen()) // 1 2 3 @结束 }
2.11 面向对象
结构体、方法、嵌入、接口需用 @嵌入 表达 Go 原生语法,但调用方法/访问字段仍可在中文区域完成:
@嵌入 type 学生 struct { 姓名 string 年龄 int } // 方法 func (s *学生) 介绍() string { return fmt.Sprintf("我叫%s,今年%d岁", s.姓名, s.年龄) } // 接口 type 介绍者 interface { 介绍() string } @结束 函数 主函数() { @嵌入 s := &学生{姓名: "张三", 年龄: 18} fmt.Println(s.介绍()) // 多态:接口变量接收结构体指针 var p 介绍者 = s fmt.Println(p.介绍()) @结束 }
| 概念 | Go 关键字 | 说明 |
|---|---|---|
| 结构体 | struct | 字段集合,定义在 @嵌入 块中 |
| 方法 | func (recv) Name() | 接收者可为值或指针 |
| 嵌入 | 匿名字段 | 组合复用,无继承 |
| 接口 | interface | 隐式实现,鸭子类型 |
2.12 错误处理
Go 风格的多返回值错误处理是首选。配合 defer 资源清理与 panic/recover 处理不可恢复错误:
函数 读文件(路径 文本) (文本, 文本) { @嵌入 data, err := os.ReadFile(路径) if err != nil { return "", err.Error() } return string(data), "" @结束 } 函数 安全执行() { @嵌入 defer func() { if r := recover(); r != nil { fmt.Println("捕获到 panic:", r) } }() panic("主动抛出") @结束 }
| 机制 | Go 关键字 | 使用场景 |
|---|---|---|
| 错误返回 | error 类型 | 可预期的错误,调用方需检查 |
| 延迟执行 | defer | 资源释放、收尾逻辑(LIFO 顺序) |
| 恐慌 | panic() | 不可恢复错误,立即终止当前函数 |
| 恢复 | recover() | 仅在 defer 中有效,捕获 panic |
2.13 包与导入
易狗IDE 提供两类导入:导入 Go 原生包(@嵌入)与导入 .elib 支持库(自动合并)。
// Go 原生包导入(必须放在文件顶部 @嵌入 块) @嵌入 import ( "fmt" "os" "strings" "strconv" ) @结束 // .elib 支持库自动可用,无需导入 // 例如 libs/golib 提供的 中文命令直接调用: 函数 主函数() { 变量 s := 取文本中间("abc[123]def", "[", "]") 打印(s) // 123 }
Go 包必须用 @嵌入 在文件顶部声明;.elib 支持库由转译器在编译时自动合并 source.eg,命令别名直接可用。用户扩展是附加的,不能替换内置库。
03 窗口设计器
可视化窗口设计器采用三栏布局:左侧组件箱 + 层级列表,中间设计画布,右侧属性面板。
3.1 内置组件箱
15 个内置组件可直接拖拽到画布:
button · edit · textarea · label · checkbox · radio · listbox · combobox · switch · slider · progress · image · tabs · card · divider
3.2 设计画布
- 网格对齐:可配置网格大小
- 对齐辅助线:拖拽时实时显示
- 等距分布提示:多选时显示
- 多选 + 对齐工具栏:左/中/右/顶/中/底 + 水平/垂直等距分布
- Tab 顺序编辑模式:切换 Tab 键焦点顺序
3.3 组件属性详解
属性面板分三组,左名右值,紧致排列:基本属性、外观属性、事件属性。下表列出所有组件通用的基本/外观属性:
| 属性 | 类型 | 说明 |
|---|---|---|
name | 文本 | 组件唯一标识,事件函数命名前缀(如 button1_onClick) |
caption | 文本 | 显示文本(按钮、标签、窗口标题等) |
x / y | 整数 | 左上角坐标(相对于父容器) |
width / height | 整数 | 宽 / 高(像素) |
visible | 布尔 | 是否可见 |
enabled | 布尔 | 是否可用(灰色禁用) |
locked | 布尔 | 设计器锁定(防止误拖动) |
font | 对象 | 字体(family/size/bold/italic) |
color | 颜色 | 背景色(#RRGGBB 或调色板) |
border | 枚举 | 边框样式(none/solid/dashed) |
radius | 整数 | 圆角像素值 |
| 组件 | 特有属性 |
|---|---|
| edit / textarea | placeholder、readonly、maxLength、multiline(仅 textarea) |
| checkbox / radio | checked、group(radio 单选组) |
| listbox / combobox | items(数组)、selected、editable(仅 combobox) |
| switch | on、label |
| slider | min、max、step、value、orientation |
| progress | value、max、indeterminate |
| image | src、fit(cover/contain/fill)、alt |
| tabs | tabs(数组:title/content)、active |
| card | title、shadow、padding |
| divider | orientation(horizontal/vertical)、thickness |
属性面板左名右值紧致排列,基本属性 / 外观属性 / 事件属性三组分开。最下方为事件下拉组合框,选择某事件时定位到窗口对应源码文件;没有事件代码时自动创建,有则直接定位显示。
3.4 组件事件
事件是组件与代码交互的入口。以下为常见事件参考,实际可用事件以属性面板事件下拉为准。事件处理函数命名规则由 IDE 在选择事件时自动生成。所有组件共享一组通用事件,部分组件另有特有事件:
| 通用事件 | 触发时机 | 典型参数 |
|---|---|---|
onClick | 单击 | 无 |
onDblClick | 双击 | 无 |
onChanged | 值改变(edit/checkbox/slider 等) | 无 |
onKeyDown | 键盘按下 | key, mods |
onKeyUp | 键盘抬起 | key, mods |
onMouseMove | 鼠标移动 | x, y |
onMouseDown | 鼠标按下 | button, x, y |
onMouseUp | 鼠标抬起 | button, x, y |
onFocus / onBlur | 获得/失去焦点 | 无 |
onResize | 尺寸改变 | w, h |
| 组件 | 特有事件 |
|---|---|
| listbox / combobox | onSelect(选中项改变) |
| tabs | onTabChange(活动标签切换) |
| slider | onSlideStart / onSlideEnd |
| image | onLoad / onError |
// 按钮1 的单击事件 函数 button1_onClick() { 打印("按钮被点击") } // 编辑框1 内容改变事件 函数 edit1_onChanged() { 打印("内容已改变") }
3.5 布局与对齐
易狗IDE 支持三种布局策略,可按场景组合使用:
| 布局方式 | 说明 | 适用场景 |
|---|---|---|
| 绝对定位 | x / y / width / height 直接指定像素 | 固定尺寸窗口、对话框、表单 |
| 锚点 anchor | 组件相对父容器四边距离固定(top/bottom/left/right) | 窗口可缩放、组件跟随 |
| 弹性布局 | 容器组件(card/tabs)内部按 row/column 流式排列 | 多组件排列、列表项 |
设计器提供完整对齐工具栏,多选组件后一键对齐:
- 水平对齐:左对齐 / 水平居中 / 右对齐
- 垂直对齐:顶对齐 / 垂直居中 / 底对齐
- 等距分布:水平等距 / 垂直等距
- 等尺寸:等宽 / 等高 / 等大小
设计画布支持可配置网格大小,拖拽时实时显示对齐辅助线,多选时显示等距分布提示,让手动排版接近专业设计工具体验。
3.6 15 个组件逐一说明
| 组件 | 用途 | 关键属性 | 关键事件 |
|---|---|---|---|
button | 按钮,触发动作 | caption / enabled | onClick / onDblClick |
edit | 单行文本输入 | placeholder / readonly / maxLength | onChanged / onKeyDown / onFocus |
textarea | 多行文本输入 | placeholder / multiline / scroll | onChanged / onKeyDown |
label | 静态文本标签 | caption / align | onClick(少用) |
checkbox | 复选框 | checked / caption | onChanged |
radio | 单选框(同组互斥) | checked / group | onChanged |
listbox | 列表框(多行选项) | items / selected / multi | onSelect / onDblClick |
combobox | 下拉组合框 | items / selected / editable | onSelect / onChanged |
switch | 开关(现代化复选) | on / label | onChanged |
slider | 滑块(范围选择) | min / max / step / value | onChanged / onSlideStart / onSlideEnd |
progress | 进度条 | value / max / indeterminate | — |
image | 图片显示 | src / fit / alt | onLoad / onError / onClick |
tabs | 标签页容器 | tabs / active | onTabChange |
card | 卡片容器(分组) | title / shadow / padding | — |
divider | 分割线 | orientation / thickness | — |
// 在窗口设计器中拖入 button,name 设为 btnOK,caption 设为 "确定" // 双击按钮或在属性面板事件下拉中选择 onClick 自动生成: 函数 btnOK_onClick() { 打印("用户点击了确定") }
3.7 .ew 窗口文件格式
.ew 是窗口设计文件,描述窗口与组件树,由窗口设计器维护。编译时由转译器生成 .ir.json 中间表示(IR),二者不同:.ew 面向设计器编辑,.ir.json 面向编译消费。IDE 自动维护,一般不需要手动编辑。以下为参考结构,实际文件格式以 IDE 生成为准:
{
"version": 1,
"type": "window",
"name": "window_main",
"caption": "我的主窗口",
"width": 800,
"height": 600,
"children": [
{
"type": "button",
"name": "btnOK",
"caption": "确定",
"x": 320,
"y": 520,
"width": 80,
"height": 32,
"events": ["onClick"]
},
{
"type": "edit",
"name": "editName",
"placeholder": "请输入姓名",
"x": 100,
"y": 100,
"width": 200,
"height": 28
}
]
}
| 字段 | 说明 |
|---|---|
version | IR 版本号,IDE 升级时向后兼容 |
type | 节点类型(window / button / edit ...) |
name | 组件唯一标识,事件函数前缀 |
children | 子组件数组(递归结构) |
events | 该组件已绑定的事件名列表 |
.ew 文件由窗口设计器维护,手动编辑可能导致 IR 与设计器不一致。如确需修改,编辑后请关闭并重新打开窗口设计器以重新加载 IR。
3.8 外置组件包
通过 components/ 目录声明式注册外置组件,支持 SVG 图标 + preview HTML:
{
"type": "datepicker",
"label": "日期选择器",
"icon": "icon.svg",
"props": { ... },
"events": [ ... ],
"preview": "preview.html"
}
preview.html 支持 {{propName}} 占位符,运行时替换为真实属性值实现真实预览。SVG 图标通过 v-html 加载并受 CSS 尺寸限制。
3.9 组件分组
同组组件共享虚线 outline,分组标识色共 8 色:红、蓝、绿、黄、紫、橙、青、粉。用于在设计器中视觉区分逻辑相关的组件(如同一表单的字段)。
04 AI 助手
AI 助手参考 Reasonix 等开源项目,深度溶合 IDE。5 Agent 协作完成开发任务。
4.1 五 Agent 角色
| 角色 | 名称 | 职责 |
|---|---|---|
| planner | 架构规划 | 分析需求、设计模块、拆分任务 |
| coder | 代码生成 | 根据规划编写 EGOU 代码 |
| reviewer | 代码审查 | 检查代码质量、风格、潜在问题 |
| ui_builder | UI 设计 | 窗口布局、控件配置、事件绑定 |
| fixer | 错误修复 | 根据编译错误诊断并修复 |
4.2 危险工具人机确认
AI 调用工具按风险分三级:
- safe — 自动执行
- moderate — 自动执行
- dangerous — 必须 30 秒内人机确认
危险工具包括:write_file / delete_file / run_build / run_command / overwrite_file。确认事件通过 ai-tool-confirm 通道从 Go 传至 JS,前端调用 ConfirmToolCall(requestID, approved) 回调。
4.3 BuildAndFix 自动修复
当编译失败时,fixer Agent 自动介入,根据编译错误诊断并修复代码:
- 默认最多 3 轮修复
- 阶段事件:
build-start→build-failed→fix-start→fix-applied→build-success/max-rounds-exceeded
4.4 AI 设置
系统设置 → AI 设置中可配置:
- 多模型添加(支持不同 LLM 提供商)
- Skills 技能库
- 智能体 Agent
- 用户规则(自定义提示词)
4.5 能力徽章
AI 能力以固定颜色徽章标识(6 种):
| 能力 | 颜色 |
|---|---|
| chat | #63b3ed 蓝 |
| code_gen | #9f7aea 紫 |
| debug | #f56565 红 |
| explain | #48bb78 绿 |
| plan | #ecc94b 黄 |
| ui_design | #ed89c1 粉 |
4.6 AI 5 Agent 角色详解
AI 助手以 5 Agent 协作模式完成开发任务,每个角色有明确的输入/输出边界:
| 角色 | 键名 | 输入 | 输出 | 典型场景 |
|---|---|---|---|---|
| 架构师 | planner | 需求描述 / 现有代码 | 模块拆分图 / 任务清单 / 接口定义 | 新项目立项、大功能重构、技术选型 |
| 编码员 | coder | 架构师的规划 / 单个任务 | .eg 源码 / .ew 设计文件 | 按规划实现功能、补全函数体 |
| 审查员 | reviewer | coder 产出的代码 | 审查意见 / 风险点 / 改进建议 | 提交前 review、规范检查、漏洞扫描 |
| UI 设计师 | ui_builder | 布局需求 / 组件规格 | .ew 设计文件 / 组件树 | 窗口布局、控件配置、事件绑定 |
| 错误修复师 | fixer | 编译错误 / 运行异常 | 修复后的代码 / 诊断报告 | BuildAndFix 自动修复、错误诊断 |
典型协作链:架构师拆分任务 → 编码员实现 → 审查员审查 → UI 设计师布局 → 错误修复师修复。任一环节发现问题可回溯到上游 Agent 修正。
4.7 AI 使用流程
- 发起对话:左侧面板切换到「AI」标签,输入问题或需求
- 指定角色:在输入框前用
@planner/@coder/@reviewer/@ui_builder/@fixer显式指定 Agent;不指定时由 AI 自动判断 - 选择上下文:用
#文件名引用具体文件作为上下文,或勾选「整项目」让 AI 全局分析 - 查看建议:AI 输出代码时,每段建议右侧有 接受 / 拒绝 按钮
- 接受修改:点击「接受」会直接写入对应文件;点击「拒绝」则丢弃该段建议
- 触发工具:AI 调用工具(read_file / write_file / run_build 等)时,按风险等级自动或人机确认执行
- 多轮迭代:基于 AI 输出继续追问,直到满意为止
@planner #src/main.eg 帮我设计一个文件浏览器功能,左侧目录树,右侧文件预览。 @coder 按 planner 的规划实现 DirTree 组件的事件绑定。 @reviewer 检查我刚写的 window_main.eg 是否有内存泄漏风险。
4.8 AI 最佳实践
| 场景 | 是否适合 AI | 说明 |
|---|---|---|
| 从零搭建 CRUD 模块 | ✅ 推荐 | 样板代码 AI 写得又快又准 |
| 中文语法→Go 包调用转换 | ✅ 推荐 | AI 熟悉 Go 生态,能推荐合适库 |
| 正则 / 复杂字符串处理 | ✅ 推荐 | AI 擅长,并能给出测试用例 |
| 窗口设计器布局初稿 | ✅ 推荐 | 让 ui_builder 角色生成 .ew 草案 |
| 核心业务算法 | ⚠️ 谨慎 | AI 写完必须人工 review,关键路径要补测试 |
| 安全相关代码(加密/鉴权) | ❌ 不推荐 | AI 可能引入漏洞,建议人工编写并审查 |
| 性能关键路径 | ❌ 不推荐 | AI 倾向写"能跑"的代码,性能需人工调优 |
| 模糊需求 / 无明确输入 | ❌ 不推荐 | AI 容易臆造,先和架构师 Agent 对齐 |
1. 明确角色:用 @角色 指定 Agent;2. 给出上下文:用 #文件 引用相关代码;3. 说清目标:写"实现 X 功能"而非"帮我看下";4. 分步迭代:复杂任务拆成多轮,每轮聚焦一步;5. 要求测试:让 AI 同时给出测试用例。
AI 调用 run_command / overwrite_file / delete_file 等危险工具时会弹出 30 秒倒计时确认框。务必核对命令内容再点确认,避免误删项目文件。
05 扩展系统
易狗IDE 有 4 种扩展机制,IDE 纯净化,所有资源外置,用户共享共建生态。
5.1 .elib 支持库
声明式提供中文命令/函数。双层结构:全局库(exe 同级 libs/)+ 项目库(<项目>/libs/)。
libs/golib/ ├── package.json # 包元信息(name/version/author) ├── commands.json # 命令定义 └── source.eg # 源码
IDE 启动时扫描 <项目>/libs/*/commands.json,与内置支持库合并。转译时自动合并 source.eg,剥离重复声明,中文别名 → 英文键映射注册到 transpiler。用户扩展是附加的,不能替换内置库。
5.2 组件包
声明式扩展窗口设计器组件,详见第 3 章 · 外置组件包。
components/<包名>/ ├── package.json # 包元数据 └── components/ └── <组件名>/ ├── config.json # type/label/icon/props/events/preview └── icon.svg # 图标(可选)
5.3 插件
编程式扩展 IDE 功能,通过 activate(api) 接口注册:
export function activate(api) { // 注册自定义左侧面板(G7 插件面板) api.registerPanel({ "icon": "🔧", "label": "我的工具", "render": renderPanel }); // 注册命令 api.registerCommand("my.cmd", handler); }
G7 插件面板按钮以 emoji 字符串为 icon,追加在左侧菜单 4 个固定标签之后。
5.4 项目模板
声明式定义新建项目模板:
{
"name": "窗口程序",
"description": "带启动窗口的桌面应用",
"files": ["main.eg", "启动窗口.eg", "启动窗口.ew"]
}
5.5 扩展机制对比
| 类型 | 目录 | 注册方式 | 用途 |
|---|---|---|---|
| 支持库 | libs/ | commands.json 声明式 | 提供中文命令/函数 |
| 组件包 | components/ | config.json 声明式 | 窗口设计器组件 |
| 插件 | plugins/ | activate(api) 编程 | IDE 功能扩展 |
| 项目模板 | templates/ | template.json 声明式 | 新建项目模板 |
5.6 .elib 支持库开发指南
支持库以目录形式组织,最小单元包含三个文件。下表说明每个文件职责:
| 文件 | 必需 | 说明 |
|---|---|---|
package.json | 是 | 包元信息(name/version/author/description) |
commands.json | 是 | 命令定义:中文名 / 英文键 / 参数签名 / 返回类型 |
source.eg | 是 | 命令实现源码,转译时合并到用户项目 |
README.md | 否 | 使用说明 |
examples/ | 否 | 示例代码 |
{
"package": "golib",
"version": "1.0.0",
"commands": [
{
"zh": "取文本中间",
"en": "GetMidStr",
"params": [
{"name": "源文本", "type": "string"},
{"name": "左边界", "type": "string"},
{"name": "右边界", "type": "string"}
],
"returns": "string"
}
]
}
函数 取文本中间(源文本, 左边界, 右边界) { @嵌入 i := strings.Index(源文本, 左边界) if i < 0 { return "" } i += len(左边界) j := strings.Index(源文本[i:], 右边界) if j < 0 { return "" } return 源文本[i : i+j] @结束 }
| 放置位置 | 作用范围 | 说明 |
|---|---|---|
全局 libs/<name>/(exe 同级) | 所有项目 | IDE 启动时扫描,与内置库合并 |
项目 <project>/libs/<name>/ | 仅当前项目 | 覆盖同名全局库的 source.eg(命令定义仍合并) |
用户扩展是附加的:同名命令时,内置库优先;用户库命令与内置库命令冲突时,用户库不会被注册。如需替换内置行为,请在用户代码中重新定义同名函数(局部覆盖)。
5.7 组件包开发
组件包通过声明式 JSON 注册自定义组件。一个组件包可包含多个组件,每个组件独立目录:
{
"type": "datepicker",
"label": "日期选择器",
"icon": "icon.svg",
"defaultSize": {"w": 160, "h": 32},
"props": {
"value": {"type": "string", "default": ""},
"format": {"type": "string", "default": "yyyy-MM-dd"},
"minDate": {"type": "string", "default": ""},
"maxDate": {"type": "string", "default": ""}
},
"events": ["onChange", "onFocus", "onBlur"],
"preview": "preview.html"
}
| 字段 | 必需 | 说明 |
|---|---|---|
type | 是 | 组件类型键,全 IDE 唯一 |
label | 是 | 组件箱显示名 |
icon | 否 | SVG 图标路径(相对组件目录) |
defaultSize | 否 | 拖入画布时的默认宽高 |
props | 是 | 属性定义,每项含 type / default |
events | 是 | 事件名列表,决定属性面板事件下拉项 |
preview | 否 | 预览 HTML 路径,支持 {{propName}} 占位 |
把整个组件包目录打包为 zip,放到 IDE 的 components/ 或项目 components/ 下重启 IDE 即可识别。可通过 GitHub 仓库分发,用户克隆到 components/ 即完成安装。
5.8 插件开发
插件通过 JS 模块的 activate(api) 入口注册 IDE 功能。API 提供注册面板、命令、菜单、状态栏等能力:
export function activate(api) { // 注册左侧面板(G7 插件面板,emoji 图标) api.registerPanel({ "icon": "📊", "label": "Git 状态", "render": renderGitPanel }); // 注册命令(Ctrl+Shift+P 调用) api.registerCommand("git.commit", commitAll); api.registerCommand("git.push", pushToRemote); // 注册菜单项 api.registerMenuItem("file/save", { "label": "提交并推送", "command": "git.commit" }); // 生命周期:插件卸载时清理 api.onDeactivate(() => { clearInterval(timer); }); } function renderGitPanel(container) { container.innerHTML = "<div>Loading...</div>"; refreshStatus(container); } function commitAll() { /* ... */ } function pushToRemote() { /* ... */ }
| API | 说明 |
|---|---|
api.registerPanel(opts) | 注册左侧面板,opts 含 icon/label/render |
api.registerCommand(id, fn) | 注册命令,可通过快捷键或命令面板触发 |
api.registerMenuItem(group, opts) | 注册菜单项到指定分组 |
api.registerStatusBar(item) | 注册状态栏项 |
api.onActivate(fn) / api.onDeactivate(fn) | 生命周期钩子 |
api.openFile(path) / api.saveFile() | 调用 IDE 内部能力 |
api.runBuild() | 触发编译(dangerous,需人机确认) |
5.9 模板开发
项目模板由 template.json + 模板文件组成,新建项目时 IDE 复制文件并替换占位符:
{
"name": "计算器",
"description": "标准计算器应用模板",
"icon": "icon.svg",
"files": ["main.eg", "window_main.eg", "window_main.ew"],
"variables": {
"PROJECT_NAME": {"prompt": "项目名称", "default": "mycalc"},
"AUTHOR": {"prompt": "作者", "default": "匿名"}
}
}
// 项目:{{PROJECT_NAME}} // 作者:{{AUTHOR}} 函数 主函数() { 打印("{{PROJECT_NAME}} 启动") }
| 占位符 | 来源 | 说明 |
|---|---|---|
{{PROJECT_NAME}} | 用户输入 | 项目名,也用于目录名 |
{{AUTHOR}} | 用户输入 | 作者名 |
{{DATE}} | 系统 | 创建日期(yyyy-MM-dd) |
{{EGOU_VERSION}} | IDE | 当前 IDE 版本号(如 v0.12.32) |
将整个模板目录打包为 zip,放到 IDE 的 templates/ 下重启 IDE 即可在新建项目对话框中看到。模板间可继承:在 template.json 中加 "extends": "blank" 可复用基础模板文件。
06 调试器
基于 Delve (dlv) headless 模式集成调试器,通过 JSON-RPC over TCP 控制。
6.1 调试架构
前端 DebugPanel.vue
↓ F5 / F10 / F11 / Shift+F11
App.vue → IDEService.DebugContinue / Next / Step / StepOut
↓
internal/app/debug.go
↓
internal/debugger/client.go(JSON-RPC over TCP)
↓
dlv headless 子进程
↓
egruntime.exe(带 DWARF 调试信息)
6.2 断点
- 设置断点:编辑器行号栏(glyph margin)点击切换,或 F9
- Shift + 点击:添加断点(与书签区分)
- 当前执行行:黄色背景高亮
- 断点按文件隔离:Editor 单实例复用,切换文件时同步断点
6.3 //line 断点映射
.eg 源码通过 //line 指令映射到生成的 Go 代码。调试时:
- 用户在
.eg文件设置断点 → 前端记录行号 - 启动调试时传给后端 → 后端通过
//line指令计算对应 Go 文件:行号 - 调用 dlv
CreateBreakpoint设置断点
6.4 入口断点
调试启动时自动在 main.mainImpl(转译后的主函数)设置断点并 continue,避免 dlv 默认停在 Go runtime 入口导致 no source for PC 错误。
6.5 调试快捷键
| 快捷键 | 功能 | 等价 dlv 命令 |
|---|---|---|
| F5 | 继续执行(DebugContinue) | continue |
| F9 | 切换当前行断点 | break / clear |
| F10 | 单步步过(Next) | next |
| F11 | 单步步入(Step) | step |
| Shift+F11 | 单步步出(StepOut) | stepout |
| Ctrl+Shift+F5 | 重启调试 | restart |
| Shift+F5 | 停止调试 | exit |
6.6 Delve 调试器配置
Delve 以 headless 子进程方式运行,IDE 通过 JSON-RPC over TCP 与之通信。配置项位于 .eg/project.eg.json:
{
"debug": {
"enabled": true,
"port": 8585,
"initBreakpoint": "main.mainImpl",
"autoContinue": true,
"maxArrayLoads": 64,
"logLevel": "info"
}
}
| 选项 | 默认值 | 说明 |
|---|---|---|
enabled | true | 是否启用调试器集成 |
port | 8585 | dlv headless 监听端口 |
initBreakpoint | main.mainImpl | 调试启动时的入口断点(避免停在 runtime) |
autoContinue | true | 设置入口断点后立即 continue |
maxArrayLoads | 64 | 切片/数组变量最多加载元素数 |
logLevel | info | dlv 日志级别(debug/info/warn/error) |
Delve 为可选依赖。如未安装,IDE 设置 → 调试器面板提供「自动安装」按钮,会执行 go install github.com/go-delve/delve/cmd/dlv@latest。也可在设置中指定 dlv 可执行文件的非 PATH 路径。
6.7 断点类型与条件断点
| 断点类型 | 设置方式 | 说明 |
|---|---|---|
| 普通断点 | 行号栏点击 / F9 | 执行到该行即暂停 |
| 条件断点 | 右键断点 → 编辑条件 | 满足表达式时才暂停,如 i == 100 |
| 命中计数断点 | 右键断点 → 命中次数 | 第 N 次命中时暂停(用于循环定位) |
| 入口断点 | 自动设置 | main.mainImpl,避免 dlv 停在 runtime |
| 函数断点 | 调试面板 → 函数名 | 按函数名设置,无需源码行号 |
// 在循环中只在 i=50 时停下 判断循环 (i < 100) { 打印(i) // ← 此行设条件断点:i == 50 i++ }
6.8 变量查看与监视
调试面板(左侧工具面板「调试」标签)分四个区域:
| 区域 | 说明 |
|---|---|
| 变量 | 当前作用域的局部变量与参数,可展开嵌套结构 |
| 监视 | 用户添加的监视表达式,每步自动求值 |
| 调用栈 | 当前调用栈,点击栈帧切换作用域 |
| 断点 | 所有断点列表,可批量启用/禁用/删除 |
- 鼠标悬停:编辑器中变量悬停 0.5s 显示当前值
- 添加监视:选中表达式右键 → 添加到监视,或直接在监视区输入
- 修改变量值:双击变量值可临时修改(仅支持基础类型)
- 展开嵌套:struct / slice / map 可递归展开,受
maxArrayLoads限制
6.9 调用栈分析
调用栈面板显示当前线程的所有栈帧,从顶(当前函数)到底(入口函数):
main.mainImpl() src/main.eg:12 ← 当前 module.计算阶乘(5) src/module/math.eg:8 module.计算阶乘(4) src/module/math.eg:8 module.计算阶乘(3) src/module/math.eg:8 main.mainImpl() src/main.eg:10 runtime.main() Go runtime
- 点击栈帧:编辑器跳转到该帧对应源码行,变量区切换到该帧作用域
- 行号映射:通过
//line指令将 Go 行号映射回.eg源码行号 - 递归识别:同函数多次出现在栈中可识别递归调用深度
切换栈帧只能查看变量与源码,不能在该帧执行表达式求值。如需检查上层帧的中间状态,建议在调用处加断点重新运行。
07 编译发布
易狗IDE 支持 debug / release 双模式编译,并提供 garble 混淆与 UPX 压缩保护用户产品。
7.1 编译模式
| 模式 | 产物名 | 特性 |
|---|---|---|
| debug | egruntime.exe | 固定文件名,保留 DWARF(可调试) |
| release | egruntime-v1.0.1-release.exe | 含版本号,patch 自增,UPX 可选,garble 可选 |
7.2 IDE 自身安全策略
- 不用 UPX:避免杀毒软件误报
- garble -tiny(不含
-literals):避免 TrojanSpy/Stealer 误报 - SHA256 校验:构建产物输出校验值
7.3 用户产品防护三档
| 档位 | garble 选项 | 说明 |
|---|---|---|
| off | — | 普通 go build,无混淆 |
| basic(默认) | garble -tiny | 基础混淆 |
| full | garble -literals -tiny | 完整混淆(含字面量) |
7.4 UPX 压缩
UPX 可选,使用修改过魔数/节区名的 upx-egou.exe,避免被逆向工具识别。每次编译注入随机密钥,防止批量逆向。IDE 本身不使用 UPX。
7.5 错误处理
编译错误采用结构化解析与中文翻译:
- 解析正则:
^(.+?):(\d+):(\d+):\s*(.+)$ - 中文翻译:20 条规则(如
undefined:→未定义:) - 前端跳转:错误条目可点击跳转到对应文件行
7.6 静态编译标志
-tags production,netgo,osusergo -trimpath -buildvcs=false \
-ldflags "-w -s"
强制使用 python scripts/build.py,禁用 PowerShell 批量写文件。
7.7 用户项目结构
myego/ ├── .eg/ # IDE 项目元数据(隐藏目录) │ ├── project.eg.json # 项目配置 │ ├── layout/ # 窗口布局描述(IR) │ │ └── mainwindow.ir.json │ ├── memory/ # AI 项目记忆 │ └── cache/ # 编译缓存 ├── src/ # 源代码目录 │ ├── main.eg # 主入口文件 │ ├── window_main.ew # 主窗口设计文件 │ ├── window_main.eg # 主窗口代码(事件处理) │ ├── module/ # 用户模块 │ └── include/ # 引用其它共享模块 ├── resources/ # 项目专用资源 ├── lib/ # 第三方库(DLL/静态库) ├── libs/ # .elib 扩展包 ├── out/ # 编译输出(临时) └── bin/ # 最终可执行文件
7.8 debug 模式详解
debug 模式面向开发调试,保留完整调试信息,编译速度快:
| 特性 | 说明 |
|---|---|
| 产物名 | egruntime.exe(固定,便于调试器复用) |
| 调试信息 | 保留 DWARF,可被 dlv 读取 |
| 编译优化 | 关闭(-gcflags="all=-N -l"),变量不被优化掉 |
| 输出目录 | 临时目录(系统 temp 下随机子目录),运行结束后清理 |
| 启动速度 | 快(无 garble / UPX 后处理) |
| 体积 | 较大(含调试符号) |
debug 产物放在系统临时目录的随机子目录下,避免污染项目 bin/。每次编译复用同一临时目录可加速增量编译,可在设置中开启「保留临时目录」便于排查。
7.9 release 模式详解
release 模式面向最终发布,产物含版本号,可选混淆压缩:
| 特性 | 说明 |
|---|---|
| 产物名 | egruntime-v<major>.<minor>.<patch>-release.exe |
| 版本号来源 | .eg/project.eg.json 的 version 字段;每次 release 编译 patch 自增 |
| 调试信息 | 剥离(-ldflags="-w -s"),体积更小 |
| 编译优化 | 开启(默认 -O2) |
| 输出目录 | 项目 bin/ |
| 混淆 | 可选 garble(off / basic / full) |
| 压缩 | 可选 UPX(upx-egou.exe,魔数改写) |
| 校验 | 输出 SHA256 校验文件(*.exe.sha256) |
bin/ ├── egruntime-v1.0.0-release.exe # 主产物 ├── egruntime-v1.0.0-release.exe.sha256 # SHA256 校验 ├── egruntime-v1.0.1-release.exe # patch 自增后 └── egruntime-v1.0.1-release.exe.sha256
7.10 编译选项表
编译选项在 .eg/project.eg.json 的 build 段配置:
| 选项 | 说明 | 示例 |
|---|---|---|
targetOS / GOOS | 目标操作系统 | windows / darwin / linux |
targetArch / GOARCH | 目标 CPU 架构 | amd64 / arm64 / 386 |
ldflags | 链接器标志 | -w -s -X main.version=1.0.0 |
tags | 构建标签 | production,netgo,osusergo |
trimpath | 移除源码路径 | true(release 默认) |
mod | 模块模式 | vendor(默认,离线) |
garble | 混淆档位 | off / basic / full |
upx | UPX 压缩 | true / false |
upxLevel | UPX 压缩级别 | 1-9(默认 7) |
启用 CGO 的项目交叉编译需要对应平台的 C 工具链。纯 Go 项目(多数易狗项目)可直接 GOOS=linux GOARCH=arm64 go build。UPX 压缩在 macOS 上对 arm64 二进制支持有限,建议在目标平台本地编译后压缩。
7.11 发布检查清单
release 编译前请逐项确认,避免发布后才发现问题:
| 检查项 | 说明 |
|---|---|
| 版本号正确 | project.eg.json 的 version 已更新,无 patch 自增冲突 |
| 资源完整 | resources/ 中图片/字体/配置已全部包含 |
| 无 debug 输出 | 代码中 打印() 调用是否需要移除或改为日志文件 |
| 图标已设置 | Windows 平台需 .syso 或 rsrc 嵌入图标 |
| 权限声明 | manifest 文件(如需管理员权限)已嵌入 |
| 混淆档位 | 商业产品建议 full,开源产品可选 off |
| UPX 选项 | 评估杀软误报风险,必要时关闭 UPX 改用 garble |
| SHA256 校验 | 发布包附带 .sha256 文件,供用户验证完整性 |
| 多平台测试 | Win/macOS/Linux 至少各跑一次冒烟测试 |
| 更新日志 | devlog.html 已添加新版本条目 |
项目根目录提供 scripts/build.py,自动化执行:清理 bin/ → release 编译 → 生成 SHA256 → 复制资源 → 打包 zip。强制使用 Python 而非 PowerShell,避免编码与文件损坏问题。
08 @嵌入混编详解
@嵌入 / @结束 是易狗IDE 的核心混编机制,让中文语法与 Go 原生代码无缝共存。本章深入讲解语法规则、错误定位与最佳实践。
8.1 @嵌入/@结束 语法规则
| 规则 | 说明 |
|---|---|
| 成对出现 | @嵌入 必须配 @结束,否则转译报错 |
| 原样保留 | 块内代码原样输出到生成的 Go 文件,不做任何转译 |
| 可嵌套位置 | 函数体内 / 函数体外(全局)/ 文件顶部(import / type / var 声明) |
| AST Token | 编译器将 @嵌入 与 @结束 作为独立 Token 识别 |
| 缩进 | 块内缩进自由,但建议比外层多 4 空格保持可读 |
| 中文符号 | 块内不进行中文符号转换,必须用英文符号 |
8.2 中文与 Go 混编示例
函数内混编(最常见,在中文函数体内插入 Go 代码片段):
函数 读配置(路径 文本) 文本 { @嵌入 data, err := os.ReadFile(路径) if err != nil { return "" } return string(data) @结束 }
全局混编(在函数外声明 type / var / import / const):
@嵌入 import ( "fmt" "os" "sync" ) type Config struct { Name string Path string } var ( cache = map[string]string{} mu sync.Mutex ) @结束 函数 主函数() { fmt.Println("OK") // 直接调用 fmt,因为已 import }
8.3 //line 错误定位指令
转译器在每个 @嵌入 块前后生成 //line 指令,让 Go 编译器与 dlv 调试器的错误/断点能映射回 .eg 源码行:
//line src/main.eg:5 func 读配置(路径 string) string { //line src/main.eg:6 data, err := os.ReadFile(路径) //line src/main.eg:7 if err != nil { //line src/main.eg:8 return "" } }
- 编译错误:Go 报错时会引用
.eg文件名与行号,IDE 输出面板点击直接跳转 - 调试断点:在
.eg设的断点通过//line映射到 Go 行号,dlv 据此停点 - 调用栈:调用栈面板显示
.eg文件:行号,而非 Go 文件
8.4 混编最佳实践
| 场景 | 建议 | 原因 |
|---|---|---|
| 简单分支/循环 | 用纯中文语法 | 可读性高,编辑器有自动展开 |
| 调用 Go 标准库 | 用 @嵌入 | 中文语法无包调用语法 |
| 声明 struct/interface | 用 @嵌入 | 中文语法不提供类型声明 |
| 声明 import | 用 @嵌入(文件顶部) | 必须用 Go 原生 import 语法 |
| 复杂业务逻辑 | 优先纯中文,必要时混编 | 保持代码风格一致 |
| 性能关键路径 | 可用 @嵌入 写 Go 原生 | 避免转译层开销(虽极小) |
| 闭包 / goroutine | 必须用 @嵌入 | 中文语法无对应关键字 |
能用纯中文表达就别用 @嵌入。@嵌入 块越少,代码风格越统一,AI 辅助也越准确。把 @嵌入 限制在「调用 Go 库」与「声明类型」两类场景即可。
8.5 常见混编陷阱与解决方案
| 陷阱 | 表现 | 解决方案 |
|---|---|---|
| 块内用了中文符号 | Go 编译报错(如 unexpected token) | @嵌入 块内必须用英文符号,关闭中文输入法 |
忘记 @结束 | 转译报错「未闭合的 @嵌入 块」 | 检查配对,IDE 会在 @嵌入 处高亮未闭合 |
| 块内引用中文变量名 | 变量可见(Go 支持 UTF-8 标识符),但 Go 库函数可能不认 | 混编块内传参时确保变量名一致 |
| import 重复声明 | Go 编译报错 imported and not used 或重复 | 转译器自动去重,但用户代码若手动 import 同一包会冲突 |
| 块内 return 但函数签名不匹配 | Go 编译报错类型不匹配 | 检查 @嵌入 块内 return 的值与函数声明的返回类型一致 |
| 行号映射错位 | 调试断点不停或停错行 | 确保 @嵌入 块前后无空行被吞掉,必要时清理缓存重编译 |
| 块内用了单引号字符串 | Go 不支持单引号字符串,报错 | 改用双引号;注意 @嵌入 块内单引号 rune 字面量(如 'f')是合法的,仅单引号字符串不合法 |
新手最常犯的错误:在 @嵌入 块内用中文逗号 ,。Go 编译器报错信息晦涩(通常为 syntax error: unexpected newline, expecting comma or )),但实际是中文逗号未被转换。建议在 IDE 设置中开启「输入法自动切换」插件,进入 @嵌入 块自动切英文。
09 实战教程:计算器应用
本章通过一个标准计算器应用,串联项目创建、窗口设计、代码编写、编译发布全流程。完成后你将掌握易狗IDE 的核心开发节奏。
9.1 创建项目
- 启动 IDE,起始页 →「创建项目」
- 模板选择「window」(窗口程序)
- 项目名填
mycalc,路径选择E:\dev\mycalc(路径避免中文与空格) - 点击「创建」,IDE 生成项目骨架:
src/main.eg+src/window_main.ew+src/window_main.eg
9.2 设计计算器界面
双击 src/window_main.ew 打开窗口设计器,按以下布局拖入组件:
| 组件 | name | caption/属性 | 位置 (x,y,w,h) |
|---|---|---|---|
| edit | editDisplay | readonly=true, align=right | 20,20,260,40 |
| button | btn7 | caption="7" | 20,80,60,40 |
| button | btn8 | caption="8" | 90,80,60,40 |
| button | btn9 | caption="9" | 160,80,60,40 |
| button | btnDiv | caption="/" | 230,80,50,40 |
| button | btn4 | caption="4" | 20,130,60,40 |
| button | btn5 | caption="5" | 90,130,60,40 |
| button | btn6 | caption="6" | 160,130,60,40 |
| button | btnMul | caption="*" | 230,130,50,40 |
| button | btn1 | caption="1" | 20,180,60,40 |
| button | btn2 | caption="2" | 90,180,60,40 |
| button | btn3 | caption="3" | 160,180,60,40 |
| button | btnSub | caption="-" | 230,180,50,40 |
| button | btn0 | caption="0" | 20,230,130,40 |
| button | btnEq | caption="=" | 160,230,60,40 |
| button | btnAdd | caption="+" | 230,230,50,40 |
| button | btnClear | caption="C" | 20,280,260,40 |
每拖入一个按钮,在属性面板 name 改为对应名称,caption 改为对应字符。设计完成后窗口大小约 300×340。
设计完成保存后,src/window_main.ew 会记录窗口组件树。以下是参考结构(实际文件格式以 IDE 生成为准):
{
"type": "window",
"name": "window_main",
"caption": "计算器",
"size": {"w": 300, "h": 340},
"children": [
{"type": "edit", "name": "editDisplay", "readonly": true, "align": "right", "rect": [20,20,260,40]},
{"type": "button", "name": "btn7", "caption": "7", "rect": [20,80,60,40]},
{"type": "button", "name": "btn8", "caption": "8", "rect": [90,80,60,40]}
]
}
.ew 是窗口设计文件,记录组件树与属性,由窗口设计器读写;.ir.json 是编译中间表示(IR),由转译器在编译时生成。两者不要混淆,用户只需编辑 .ew。
9.3 编写计算逻辑
双击 src/window_main.eg,IDE 自动生成空事件函数。补充逻辑:
// 计算器状态(变量声明用类型推断,不写中文类型别名) 变量 当前显示 = "" 变量 上一个操作数 = "" 变量 待执行运算 = "" 变量 刚按等号 = 假 // 数字按钮事件 函数 btn0_onClick() { 追加数字("0") } 函数 btn1_onClick() { 追加数字("1") } 函数 btn2_onClick() { 追加数字("2") } 函数 btn3_onClick() { 追加数字("3") } 函数 btn4_onClick() { 追加数字("4") } 函数 btn5_onClick() { 追加数字("5") } 函数 btn6_onClick() { 追加数字("6") } 函数 btn7_onClick() { 追加数字("7") } 函数 btn8_onClick() { 追加数字("8") } 函数 btn9_onClick() { 追加数字("9") } 函数 追加数字(d) { 如果 (刚按等号) { 当前显示 = "" 刚按等号 = 假 } 当前显示 = 当前显示 + d 刷新显示() } // 运算符按钮事件 函数 btnAdd_onClick() { 选择运算("+") } 函数 btnSub_onClick() { 选择运算("-") } 函数 btnMul_onClick() { 选择运算("*") } 函数 btnDiv_onClick() { 选择运算("/") } // 且/或是仿易语言别名,以转译器实际支持为准;此处用嵌套如果替代 函数 选择运算(op) { 如果 (待执行运算 != "") { 如果 (当前显示 != "") { 计算结果() } } 上一个操作数 = 当前显示 待执行运算 = op 当前显示 = "" } // 等号按钮 函数 btnEq_onClick() { 计算结果() 刚按等号 = 真 } 函数 计算结果() { 如果 (上一个操作数 == "") { 返回 } 如果 (待执行运算 == "") { 返回 } 如果 (当前显示 == "") { 返回 } @嵌入 // @嵌入块内是 Go 原生代码,可用 && || ! 逻辑运算符 // 中文变量名由转译器处理,可直接引用 a, err1 := strconv.ParseFloat(上一个操作数, 64) b, err2 := strconv.ParseFloat(当前显示, 64) if err1 != nil || err2 != nil { 当前显示 = "错误" return } var r float64 switch 待执行运算 { case "+": r = a + b case "-": r = a - b case "*": r = a * b case "/": if b == 0 { 当前显示 = "除数不能为零" return } r = a / b } 当前显示 = strconv.FormatFloat(r, 'f', -1, 64) @结束 刷新显示() 上一个操作数 = "" 待执行运算 = "" } // 清除按钮 函数 btnClear_onClick() { 当前显示 = "" 上一个操作数 = "" 待执行运算 = "" 刚按等号 = 假 刷新显示() } // 刷新编辑框显示 函数 刷新显示() { 设置组件属性("editDisplay", "text", 当前显示) }
事件函数名(如 btn7_onClick)与组件 name + 事件名的对应关系、函数签名、绑定方式均由 IDE 在窗口设计器属性面板中自动生成,无需手写。实际命名格式与可绑定事件以 IDE 属性面板/支持库为准。
示例中保留了 真/假 作为布尔字面量。布尔值推荐使用 true/false,或调用 krnln 支持库的 取常量_真() / 取常量_假()。真/假 是否作为关键字以转译器实际支持为准。
设置组件属性(组件名, 属性名, 值) 是 egou 支持库 widget 分类下的命令,实际命令名、参数顺序与可用属性以 egou 支持库 widget 分类为准。也可在窗口设计器属性面板直接设置。
@嵌入 块内是 Go 原生代码,'f' 是合法的 Go rune 字面量(表示 FormatFloat 的格式动词);中文代码区则禁止单引号字符串。@嵌入 块内引用中文变量名(如 上一个操作数)由转译器自动处理,可直接使用。
9.4 编译运行
- 标题栏模式切换选「debug」
- 点击编译运行按钮(播放图标)
- 编译输出面板显示转译 + go build 进度
- 成功后弹出计算器窗口,测试各按钮功能
- 如有 bug,在
计算结果函数内 F9 设断点,F5 启动调试
9.5 发布应用
- 测试通过后,模式切换选「release」
- 在
.eg/project.eg.json设置version: "1.0.0"、garble: "basic"、upx: true - 点击编译按钮,生成
bin/egruntime-v1.0.0-release.exe - 双击 exe 验证最终发布版可独立运行
- 分发
egruntime-v1.0.0-release.exe+.sha256给用户
如最终 exe 被杀软误报,先关闭 UPX 重编译;若仍误报,把 garble 从 full 降到 basic(移除 -literals)。商业发布建议申请杀软白名单。
10 常见问题 FAQ
本章汇总用户高频问题与解决方案,按类别组织。遇到问题先在这里查找,多数情况已有标准答案。
10.1 编译相关
| 问题 | 原因 | 解决方案 |
|---|---|---|
编译卡在 downloading 不动 | Go 默认走 GOPROXY,网络受限 | 易狗项目用 -mod=vendor,无需 GOPROXY。检查 wails-template/vendor/ 是否完整;如缺失,从备份恢复或重新安装 IDE |
cannot find package | vendor 缺包或 go.mod 损坏 | 不要手动改 go.mod;在 IDE 设置中点「修复依赖」让 IDE 重建 vendor |
undefined: fmt | 用了 Go 包但未 import | 在文件顶部用 @嵌入 块声明 import "fmt" |
| Go SDK 版本太低 | IDE 要求 1.25.0+,本机装的旧版 | 设置 → Go SDK 路径,指向新版 Go;或下载 IDE 自带的 Go SDK 包 |
| release 编译慢 | garble + UPX 双重处理耗时 | 开发阶段用 debug;release 关闭 UPX 仅用 garble 可加速 2-3 倍 |
| 交叉编译报 CGO 错误 | 项目用了 CGO(如 sqlite) | 安装目标平台 C 工具链;或在目标平台本地编译 |
10.2 运行相关
| 问题 | 原因 | 解决方案 |
|---|---|---|
egruntime.exe 找不到 | debug 模式产物在临时目录,被清理 | 重新点击编译运行;或在设置中开启「保留临时目录」 |
| 程序启动闪退 | 主函数 panic,无 recover | 在 主函数 加 defer recover;或用 debug 模式启动看输出面板错误 |
| 路径含中文导致报错 | Go 工具链对中文路径支持有限 | 项目路径避免中文与空格;可用 E:\dev\mycalc 而非 E:\我的项目\计算器 |
| 窗口一闪而过 | 主函数末尾未等待 | Wails 窗口程序主函数会自动阻塞;控制台程序需 打印() 后加等待 |
| 中文乱码 | 控制台编码非 UTF-8 | Windows 控制台执行 chcp 65001;或用日志文件输出替代控制台 |
| 调试器启动失败 | dlv 未安装或端口被占 | 设置 → 调试器 → 自动安装 dlv;或修改 port: 8586 避开占用 |
10.3 设计器相关
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 组件不显示在画布 | 组件 visible=false 或被其他组件遮挡 | 检查属性面板 visible;用层级列表调整 z-order |
| 属性面板空白 | 未选中任何组件 | 在画布或层级列表点选组件;点空白处会取消选中 |
| 事件下拉为空 | 组件未保存到 .ew | Ctrl+S 保存窗口设计文件,重新选中组件 |
| 拖拽组件位置不对 | 父容器不是预期的 | 拖拽前先点选目标父容器(如 card),新组件会成为其子节点 |
| 设计器打不开 .ew | IR 文件损坏 | 从备份恢复;或用文本编辑器打开 .ew 检查 JSON 合法性 |
| 对齐工具栏灰色 | 未多选组件 | Shift+点击 或 框选多个组件后对齐工具栏激活 |
10.4 AI 相关
| 问题 | 原因 | 解决方案 |
|---|---|---|
| AI 无响应 | 模型 API key 未配置或网络问题 | 设置 → AI 设置 → 添加模型并填 key;测试网络连通性 |
| AI 响应很慢 | 模型本身的推理速度 | 换用更快的模型;或在 AI 设置中关闭「深度思考」 |
| AI 写的代码无法编译 | AI 不熟悉易狗语法细节 | 用 @coder 角色而非通用 chat;提供 #示例.eg 作为参考 |
| Agent 切换无效 | 角色名拼写错误 | 使用 @planner / @coder / @reviewer / @ui_builder / @fixer 五个标准名 |
| AI 误删文件 | 危险工具未确认就执行 | 检查 30 秒倒计时确认框;可在设置中关闭 dangerous 自动执行 |
| BuildAndFix 死循环 | fixer 改不动同一错误 | 默认最多 3 轮;如需手动介入,按 Esc 中断 AI |
10.5 扩展相关
| 问题 | 原因 | 解决方案 |
|---|---|---|
| .elib 加载失败 | commands.json 格式错误 | 用 JSON 校验工具检查;查看 IDE 启动日志(输出面板 → 系统) |
| 自定义命令不识别 | source.eg 未实现该命令 | commands.json 中声明的命令必须在 source.eg 有对应 函数 实现 |
| 组件包冲突 | 两个包定义了同 type | 检查 config.json 的 type 全局唯一;移除冲突包 |
| 插件 activate 不执行 | JS 语法错误或 API 调用错 | 打开开发者工具(Ctrl+Shift+I)看 console 错误 |
| 模板新建项目缺文件 | template.json files 列表不全 | 补全 files 数组;模板目录下文件需与列表一致 |
| 升级 IDE 后扩展失效 | API 不兼容 | 查看 devlog.html 的破坏性变更;按新版 API 调整扩展 |
1. 官方文档:本页面就是完整文档;2. 开发日志:devlog.html 记录每次更新;3. QQ 群:1071098978 技术交流;4. GitHub Issues:提交 bug 与建议。