首页
/ Lazydocker 西班牙语快捷键手册解读:按键绑定定义、视图分区与 cheatsheet 自动生成机制

Lazydocker 西班牙语快捷键手册解读:按键绑定定义、视图分区与 cheatsheet 自动生成机制

2026-09-05 15:36:40作者:翟江哲Frasier

docs/keybindings/Keybindings_es.md 是 Lazydocker 官方维护的西班牙语快捷键速查表(cheatsheet),覆盖了项目管理、容器、服务、镜像、卷、网络六大面板以及主面板与全局按键的完整映射。读完本文,你不仅能把这张快捷键表当作日常操作的速查手册使用,还能理解这些按键是如何在 pkg/gui/keybindings.go 中逐一定义与注册的,以及这份文档是如何由 pkg/cheatsheet/generate.go 自动从源码"翻译"生成、并在 CI 中做一致性校验的。

文档定位:一份自动生成的速查表

该文件首行就声明了自身的来历:

This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run go run scripts/cheatsheet/main.go generate from the project root.

也就是说,这份西班牙语手册(以及 docs/keybindings/ 目录下德语、英语、法语、荷兰语、波兰语、葡萄牙语、土耳其语、中文等 9 种语言的姊妹文件)并不是人手撰写的,而是由快捷键注册代码加上 pkg/i18n 国际化词条共同生成的产物。要修改按键说明文字,正确姿势是修改 pkg/i18n 目录下的翻译文件(例如西语词条位于 pkg/i18n/spanish.go),然后在项目根目录运行:

go run scripts/cheatsheet/main.go generate

命令行入口 scripts/cheatsheet/main.go 支持两个子命令:

  • generate:调用 pkg/cheatsheetGenerate(),为所有语言重新生成 Keybindings_{lang}.md
  • check:调用 Check(),把仓库中已提交的 cheatsheet 与刚刚重新生成的内容做 diff,不一致则以退出码 1 报错(CI 中即依赖这一点,见 .github/workflows/ci.yml 中执行的 go run scripts/cheatsheet/main.go check)。

完整快捷键总表(继承自原文档)

下表完整保留了 Keybindings_es.md 中各章节的全部按键条目:按键 列与原文档一致,西班牙语说明 为原文档的 kbd 描述,中文释义 为便于中文读者理解的对应功能(与英文手册 docs/keybindings/Keybindings_en.md 及源码 Description 字段的英文文案一致)。

各面板通用按键(所有侧边面板均支持)

西班牙语手册中 Proyecto、Contenedores、Servicios、Imágenes、Volúmenes、Redes 六个章节的末尾都重复出现同样的 4 条按键,对应源码中按面板循环注册的绑定(见下文"公共绑定的循环注册"一节):

按键 西班牙语说明 中文释义
enter enfocar panel principal 聚焦主面板(右侧详情/日志区域)
[ anterior pestaña 切换到上一个上下文标签页
] siguiente pestaña 切换到下一个上下文标签页
/ filtrar lista 对当前列表启用过滤

Proyecto(项目/Compose 项目面板)

按键 西班牙语说明 中文释义
e editar configuración de lazydocker 编辑 Lazydocker 配置文件
o abrir configuración de lazydocker 打开 Lazydocker 配置文件
m ver logs 查看(项目全部服务的)日志

对应源码中的注册(pkg/gui/keybindings.go#L131-L151):

{
    ViewName:    "project",
    Key:         'e',
    Modifier:    gocui.ModNone,
    Handler:     gui.handleEditConfig,
    Description: gui.Tr.EditConfig,
},
{
    ViewName:    "project",
    Key:         'o',
    ...
    Handler:     gui.handleOpenConfig,
},
{
    ViewName:    "project",
    Key:         'm',
    ...
    Handler:     gui.handleViewAllLogs,
},

Contenedores(容器面板)

按键 西班牙语说明 中文释义
d borrar 删除容器(弹出确认菜单)
e esconder/mostrar contenedores parados 隐藏/显示已停止的容器
p pausa 暂停容器
s parar 停止容器
r reiniciar 重启容器
a attach 附加到容器(交互接管)
m ver logs 查看容器日志
E ejecutar shell 在容器内执行 shell
c ejecutar comando personalizado 执行预定义自定义命令
b ver comandos masivos 查看批量操作命令
w abrir en navegador (first port is http) 在浏览器中打开(假定第一个映射端口为 http)

这 11 个按键在 pkg/gui/keybindings.go#L188-L264 中逐一注册到 ViewName: "containers" 视图上,Handler 分别指向 handleContainersRemoveMenuhandleHideStoppedContainershandleContainerPausehandleContainerStophandleContainerRestarthandleContainerAttachhandleContainerViewLogshandleContainersExecShellhandleContainersCustomCommandhandleContainersBulkCommandhandleContainersOpenInBrowserCommand

一个值得注意的细节:w 键的西语描述 abrir en navegador (first port is http) 中括号内保留了英文原文。这是因为 pkg/i18n/spanish.go#L75OpenInBrowser 词条本身即为该混合文案,cheatsheet 只是原样输出翻译词条。

Servicios(Compose 服务面板)

按键 西班牙语说明 中文释义
u levantar servicio 启动(up)单个服务
d borrar contenedores 删除服务对应的容器
s parar 停止服务
p pausa 暂停服务
r reiniciar 重启服务
S iniciar 启动(start)已创建的服务
a attach 附加到服务容器
m ver logs 查看服务日志(渲染到主面板)
U levantar proyecto 启动整个项目(up project)
D dar de baja el proyecto 拆除整个项目(down project)
R ver opciones de reinicio 查看重启选项菜单
c ejecutar comando personalizado 执行预定义自定义命令
b ver comandos masivos 查看批量操作命令
E ejecutar shell 在服务容器内执行 shell
w abrir en navegador (first port is http) 在浏览器中打开

注册代码位于 pkg/gui/keybindings.go#L265-L369。注意服务面板区分了 s(stop)与 S(start)、u(up 服务)与 U(up 项目)、d(删容器)与 D(down 项目):小写面向单个服务,大写面向整个 Compose 项目,这是西语手册中最容易混淆的一组按键。

Imágenes / Volúmenes / Redes(镜像、卷、网络面板)

这三个资源面板的专属按键完全同构,每个面板只有 3 个业务按键,其余均为通用按键:

按键 西班牙语说明 中文释义
c ejecutar comando personalizado 执行预定义自定义命令
d limpiar imagen / volúmen / red 删除镜像 / 清理卷 / 删除网络
b ver comandos masivos 查看批量操作命令

对应 pkg/gui/keybindings.go#L370-L431imagesvolumesnetworks 三个 ViewName 下的注册,处理器分别为 handleImagesCustomCommand/handleImagesRemoveMenu/handleImagesBulkCommandhandleVolumes*handleNetworks* 系列。

Inicio(主面板)与 Global(全局)

按键 西班牙语说明 中文释义
esc regresar 返回(退出主面板的日志/详情视图,回到列表)
+ next screen mode (normal/half/fullscreen) 切换下一个屏幕模式(normal/half/fullscreen)
_ prev screen mode 切换上一个屏幕模式
1 focus projects panel 聚焦 Projects 面板
2 focus services panel 聚焦 Services 面板
3 focus containers panel 聚焦 Containers 面板
4 focus images panel 聚焦 Images 面板
5 focus volumes panel 聚焦 Volumes 面板
6 focus networks panel 聚焦 Networks 面板

注意 "Inicio" 一节只列出了 esc 一个按键:主面板同样绑定了 j/k 上下滚动、h/l 与方向键左右滚动,但这些绑定没有 Description 字段,因此不会出现在 cheatsheet 里(原因见下文"只收录有描述词条的绑定")。

按键在源码中的定义:Binding 结构与注册流程

西语手册的每一行都对应 pkg/gui/keybindings.go 中一条 Binding 记录。核心结构体定义在 pkg/gui/keybindings.go#L12-L18

// Binding - a keybinding mapping a key and modifier to a handler. The keypress
// is only handled if the given view has focus, or handled globally if the view
// is ""
type Binding struct {
    ViewName    string
    Handler     func(*gocui.Gui, *gocui.View) error
    Key         interface{} // FIXME: find out how to get `gocui.Key | rune`
    Modifier    gocui.Modifier
    Description string
}

三个关键字段决定了快捷键手册的形态:

  • ViewName:绑定生效的视图。为空字符串时表示"全局"(任意视图下都生效,如 escqCtrl+CPgUp/PgDn 等);非空时(如 "containers")只有该视图获得焦点时按键才被处理。这正是手册按面板分章节的根本原因;
  • Key:按键本身,既可以是 rune(如 'd'),也可以是 gocui 的专用键(如 gocui.KeyEscgocui.KeyEnter);
  • Description:取自 gui.Tr 国际化词条的说明文本,是 cheatsheet 中"按键: 说明"里冒号右边的文字。

GetKey() 方法(pkg/gui/keybindings.go#L21-L54)负责把按键值转成手册里显示的文本:27 显示为 esc13 显示为 enter、方向键显示为 ▲▼◄► 等,其余按键直接按字符输出。

应用启动时,keybindings() 函数(pkg/gui/keybindings.go#L593-L607)遍历 GetInitialKeybindings() 返回的全部绑定并调用 gocui 的 SetKeybinding 逐一注册——也就是说手册中列出的按键与运行时行为来自同一份数据源,天然一致。

公共绑定的循环注册:为什么六个章节的结尾一模一样

手册里每个侧边面板章节末尾都有 enter / [ / ] / / 四条相同按键。这不是文档模板复制,而是 pkg/gui/keybindings.go#L552-L588 中的循环逻辑:

for _, panel := range gui.allSidePanels() {
    bindings = append(bindings,
        &Binding{
            ViewName:    panel.GetView().Name(),
            Key:         gocui.KeyEnter,
            ...
            Handler:     gui.handleEnterMain,
            Description: gui.Tr.FocusMain,
        },
        &Binding{
            ViewName:    panel.GetView().Name(),
            Key:         '[',
            ...
            Handler:     wrappedHandler(panel.HandlePrevMainTab),
        },
        &Binding{... Key: ']', ... Handler: wrappedHandler(panel.HandleNextMainTab) ...},
    )
}

for _, panel := range gui.allListPanels() {
    if !panel.IsFilterDisabled() {
        bindings = append(bindings, &Binding{
            ViewName:    panel.GetView().Name(),
            Key:         '/',
            Handler:     wrappedHandler(gui.handleOpenFilter),
            Description: gui.Tr.LcFilter,
        })
    }
}
  • allSidePanels() 包含 Projects、Services、Containers、Images、Volumes、Networks 六个面板,因此每个面板都得到 enter(聚焦主面板)、[(上一个上下文标签)、](下一个上下文标签)三条绑定;
  • / 过滤绑定只追加给"未禁用过滤"的列表面板(!panel.IsFilterDisabled()),过滤的交互实现(打开过滤提示、Enter 提交、Esc 取消)位于 pkg/gui/filtering.go
  • 数字键 16 快速跳转面板的绑定同样因为 ViewName 为空而归入手册的 "Global" 章节,注册代码见 pkg/gui/keybindings.go#L537-L544

此外,每个侧边面板还循环绑定了 Tab/Backtab/h/l 与左右方向键用于在面板间切换焦点(pkg/gui/keybindings.go#L514-L523),j/k、上下方向键、鼠标滚轮与左键点击用于列表行移动(pkg/gui/keybindings.go#L525-L534)——这些绑定因缺少 Description 而未出现在手册中。

文档是如何生成的:headless GUI + 国际化词条

Keybindings_es.md 的生成链路是理解"为什么它是文档、却不会过期"的关键,核心在 pkg/cheatsheet/generate.go#L35-L61

func generateAtDir(dir string) {
    mConfig, err := config.NewAppConfig("lazydocker", "", "", "", "", true, nil, "", "")
    ...
    for lang := range i18n.GetTranslationSets() {
        os.Setenv("LC_ALL", lang)
        mApp, _ := app.NewApp(mConfig)
        mApp.Gui.SetupFakeGui()
        file, err := os.Create(dir + "/Keybindings_" + lang + ".md")
        ...
        bindingSections := getBindingSections(mApp)
        content := formatSections(mApp, bindingSections)
        ...
    }
}

分步骤解读:

  1. 按语言循环i18n.GetTranslationSets() 返回项目支持的全部语言,每个语言先 os.Setenv("LC_ALL", lang) 切换 locale,使得 mApp.Tr.* 词条取到对应语言(西语即 es,对应 pkg/i18n/spanish.go 中的词条);
  2. 搭建无头 GUISetupFakeGui()pkg/gui/gui.go#L498-L514)以 Headless: true 启动一个 gocui 实例并 createAllViews() + setPanels()。源码注释写得很直白:需要真实的视图和面板存在,才能知道到底有哪些按键。这正是上文那些"按面板循环"绑定的注册前提;
  3. 只收录有描述词条的绑定getBindingSections()pkg/cheatsheet/generate.go#L78-L106)遍历 GetInitialKeybindings()binding.Description == "" 的直接跳过——这解释了手册中看不到 q(退出)、x/?(打开选项菜单)、J/K/H/L 滚动、Tab 等已注册按键的原因;
  4. 视图名到章节标题的本地化映射viewName 为空时归入 global,其余经 titleMap 映射到本地化标题(如 mApp.Tr.ContainersTitle 在西语下即 ContenedoresmainInicio),章节标题与手册正文因此全部是西语;
  5. 格式化输出formatSections()pkg/cheatsheet/generate.go#L128-L141)用 formatBinding() 逐行输出 <kbd>按键</kbd>: 说明,包裹在 <pre> 块中,并在文首加上那段 auto-generated 提示。

一致性校验:check 命令与 CI 集成

防止"改了按键忘了重新生成文档"的机制在 pkg/cheatsheet/validate.go#L15-L60

func Check() {
    dir := GetKeybindingsDir()                       // <repo>/docs/keybindings
    tmpDir := filepath.Join(os.TempDir(), "lazydocker_cheatsheet")
    ...
    generateAtDir(tmpDir)                            // 在临时目录重新生成一套
    actualContent := obtainContent(dir)             // 读仓库里的 Keybindings_*.md
    expectedContent := obtainContent(tmpDir)
    if actualContent != expectedContent {
        difflib.WriteUnifiedDiff(...)               // 打印统一 diff
        fmt.Printf("\nCheatsheets are out of date. Please run `%s` ...", generateCheatsheetCmd)
        os.Exit(1)
    }
    fmt.Println("\nCheatsheets are up to date")
}

即:在临时目录用当前源码重新生成全部语言的手册,与 docs/keybindings/ 中已提交的内容逐字符比较,有差异就输出 unified diff 并退出码 1。该检查已被接入 CI(.github/workflows/ci.yml 中执行 go run scripts/cheatsheet/main.go check),保证仓库里每一份 Keybindings_*.md 始终与 pkg/gui/keybindings.go 中的真实绑定同步。

日常使用与更新要点

  • 使用:把 Keybindings_es.md 当作西语界面下的速查表;同一目录下的 Keybindings_en.mdKeybindings_zh.md 等 8 份文档结构与条目一一对应,差异仅在文案语言。按键与面板的对应关系由 ViewName 决定,只有当该面板视图持有焦点时按键才生效。
  • 更新:若要新增/修改按键的说明文案,应修改 pkg/i18n 对应语言的词条(而非手改 Markdown);若要新增按键本身,需在 GetInitialKeybindings() 中注册 Binding 并设置 Description,随后运行 go run scripts/cheatsheet/main.go generate 重新生成九份文档,提交前可用 ... check 验证。
  • 适用前提:上述按键集合与章节划分以当前仓库源码为准,随 GetInitialKeybindings() 的演进而变化;w 键"以第一个映射端口为 http"的行为、过滤/上下文标签页等能力均以实际注册的处理器实现(如 pkg/commands/container.gopkg/gui/panels/ 中的面板逻辑)为准。
登录后查看全文
热门项目推荐
相关项目推荐