diff --git a/docs/keybindings/Keybindings_en.md b/docs/keybindings/Keybindings_en.md index 662631386..6f8c12966 100644 --- a/docs/keybindings/Keybindings_en.md +++ b/docs/keybindings/Keybindings_en.md @@ -269,7 +269,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct ►: select next hunk ctrl+o: copy the selected text to the clipboard e: edit file - o: open file v: toggle drag select V: toggle drag select a: toggle select hunk diff --git a/docs/keybindings/Keybindings_nl.md b/docs/keybindings/Keybindings_nl.md index dd9c45ad2..c0accbd44 100644 --- a/docs/keybindings/Keybindings_nl.md +++ b/docs/keybindings/Keybindings_nl.md @@ -41,6 +41,46 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct [: vorige tabblad +## Bestanden Paneel (Bestanden) + +
+ ctrl+o: kopieer de bestandsnaam naar het klembord + ctrl+w: Toggle whether or not whitespace changes are shown in the diff view + d: bekijk 'veranderingen ongedaan maken' opties + space: toggle staged + ctrl+b: Filter files (staged/unstaged) + c: commit veranderingen + w: commit veranderingen zonder pre-commit hook + A: wijzig laatste commit + C: commit veranderingen met de git editor + e: verander bestand + o: open bestand + i: voeg toe aan .gitignore + r: refresh bestanden + s: stash-bestanden + S: bekijk stash opties + a: toggle staged alle + enter: stage individuele hunks/lijnen + g: bekijk upstream reset opties + D: bekijk reset opties + `: toggle bestandsboom weergave + M: open external merge tool (git mergetool) + f: fetch ++ +## Bestanden Paneel (Submodules) + +
+ ctrl+o: kopieer submodule naam naar klembord + enter: enter submodule + d: remove submodule + u: update submodule + n: voeg nieuwe submodule toe + e: update submodule URL + i: initialiseer submodule + b: bekijk bulk submodule opties ++ ## Branches Paneel (Branches Tabblad)
@@ -178,46 +218,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct @: open command log menu-## Bestanden Paneel (Bestanden) - -
- ctrl+o: kopieer de bestandsnaam naar het klembord - ctrl+w: Toggle whether or not whitespace changes are shown in the diff view - d: bekijk 'veranderingen ongedaan maken' opties - space: toggle staged - ctrl+b: Filter files (staged/unstaged) - c: commit veranderingen - w: commit veranderingen zonder pre-commit hook - A: wijzig laatste commit - C: commit veranderingen met de git editor - e: verander bestand - o: open bestand - i: voeg toe aan .gitignore - r: refresh bestanden - s: stash-bestanden - S: bekijk stash opties - a: toggle staged alle - enter: stage individuele hunks/lijnen - g: bekijk upstream reset opties - D: bekijk reset opties - `: toggle bestandsboom weergave - M: open external merge tool (git mergetool) - f: fetch -- -## Bestanden Paneel (Submodules) - -
- ctrl+o: kopieer submodule naam naar klembord - enter: enter submodule - d: remove submodule - u: update submodule - n: voeg nieuwe submodule toe - e: update submodule URL - i: initialiseer submodule - b: bekijk bulk submodule opties -- ## Hoofd Paneel (Mergen)
@@ -269,7 +269,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct ►: selecteer de volgende hunk ctrl+o: copy the selected text to the clipboard e: verander bestand - o: open bestand v: toggle drag selecteer V: toggle drag selecteer a: toggle selecteer hunk diff --git a/docs/keybindings/Keybindings_pl.md b/docs/keybindings/Keybindings_pl.md index 2d032e5e3..9c619f6b6 100644 --- a/docs/keybindings/Keybindings_pl.md +++ b/docs/keybindings/Keybindings_pl.md @@ -41,6 +41,56 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct [: previous tab+## Commity Panel (Commity) + +
+ ctrl+o: copy commit SHA to clipboard + ctrl+r: reset cherry-picked (copied) commits selection + b: view bisect options + s: ściśnij + f: napraw commit + r: zmień nazwę commita + R: zmień nazwę commita w edytorze + d: usuń commit + e: edytuj commit + p: wybierz commit (podczas zmiany bazy) + F: utwórz commit naprawczy dla tego commita + S: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash) + ctrl+j: przenieś commit 1 w dół + ctrl+k: przenieś commit 1 w górę + A: popraw commit zmianami z poczekalni + t: odwróć commit + n: create new branch off of commit + c: kopiuj commit (przebieranie) + C: kopiuj zakres commitów (przebieranie) + v: wklej commity (przebieranie) + ctrl+l: open log menu + g: zresetuj do tego commita + space: checkout commit + T: tag commit + ctrl+y: copy commit message to clipboard + o: open commit in browser + enter: przeglądaj pliki commita ++ +## Commity Panel (Reflog Tab) + +
+ ctrl+o: copy commit SHA to clipboard + space: checkout commit + g: wyświetl opcje resetu + c: kopiuj commit (przebieranie) + C: kopiuj zakres commitów (przebieranie) + ctrl+r: reset cherry-picked (copied) commits selection + enter: przeglądaj pliki commita ++ +## Extras Panel + +
+ @: open command log menu ++ ## Gałęzie Panel (Branches Tab)
@@ -109,73 +159,69 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct enter: view commits-## Pliki commita Panel +## Główne Panel (Patch Building)
- ctrl+o: copy the committed file name to the clipboard -- -## Pliki commita Panel (Pliki commita) - -
- c: plik wybierania - d: porzuć zmiany commita dla tego pliku + esc: wyście z trybu "linia po linii" o: otwórz plik + ▲: poprzednia linia + ▼: następna linia + ◄: poprzedni kawałek + ►: następny kawałek + ctrl+o: copy the selected text to the clipboard + space: add/remove line(s) to patch + v: toggle drag select + V: toggle drag select + a: toggle select hunk ++ +## Główne Panel (Poczekalnia) + +
+ esc: wróć do panelu plików + space: toggle line staged / unstaged + d: delete change (git reset) + tab: switch to other panel + o: otwórz plik + ▲: poprzednia linia + ▼: następna linia + ◄: poprzedni kawałek + ►: następny kawałek + ctrl+o: copy the selected text to the clipboard e: edytuj plik - space: toggle file included in patch - a: toggle all files included in patch - enter: enter file to add selected lines to the patch (or toggle directory collapsed) - `: toggle file tree view + v: toggle drag select + V: toggle drag select + a: toggle select hunk + c: Zatwierdź zmiany + w: zatwierdź zmiany bez skryptu pre-commit + C: Zatwierdź zmiany używając edytora-## Commity Panel (Commity) +## Główne Panel (Scalanie)
- ctrl+o: copy commit SHA to clipboard - ctrl+r: reset cherry-picked (copied) commits selection - b: view bisect options - s: ściśnij - f: napraw commit - r: zmień nazwę commita - R: zmień nazwę commita w edytorze - d: usuń commit - e: edytuj commit - p: wybierz commit (podczas zmiany bazy) - F: utwórz commit naprawczy dla tego commita - S: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash) - ctrl+j: przenieś commit 1 w dół - ctrl+k: przenieś commit 1 w górę - A: popraw commit zmianami z poczekalni - t: odwróć commit - n: create new branch off of commit - c: kopiuj commit (przebieranie) - C: kopiuj zakres commitów (przebieranie) - v: wklej commity (przebieranie) - ctrl+l: open log menu - g: zresetuj do tego commita - space: checkout commit - T: tag commit - ctrl+y: copy commit message to clipboard - o: open commit in browser - enter: przeglądaj pliki commita + esc: wróć do panelu plików + M: open external merge tool (git mergetool) + space: wybierz kawałek + b: wybierz wszystkie kawałki + ◄: poprzedni konflikt + ►: następny konflikt + ▲: wybierz poprzedni kawałek + ▼: wybierz następny kawałek + z: cofnij-## Commity Panel (Reflog Tab) +## Główne Panel (Zwykłe)
- ctrl+o: copy commit SHA to clipboard - space: checkout commit - g: wyświetl opcje resetu - c: kopiuj commit (przebieranie) - C: kopiuj zakres commitów (przebieranie) - ctrl+r: reset cherry-picked (copied) commits selection - enter: przeglądaj pliki commita + mouse wheel down: przewiń w dół (fn+up) + mouse wheel up: przewiń w górę (fn+down)-## Extras Panel +## Menu Panel
- @: open command log menu + esc: close menu## Pliki Panel (Pliki) @@ -218,70 +264,23 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct b: view bulk submodule options -## Główne Panel (Scalanie) +## Pliki commita Panel
- esc: wróć do panelu plików - M: open external merge tool (git mergetool) - space: wybierz kawałek - b: wybierz wszystkie kawałki - ◄: poprzedni konflikt - ►: następny konflikt - ▲: wybierz poprzedni kawałek - ▼: wybierz następny kawałek - z: cofnij + ctrl+o: copy the committed file name to the clipboard-## Główne Panel (Zwykłe) +## Pliki commita Panel (Pliki commita)
- mouse wheel down: przewiń w dół (fn+up) - mouse wheel up: przewiń w górę (fn+down) -- -## Główne Panel (Patch Building) - -
- esc: wyście z trybu "linia po linii" + c: plik wybierania + d: porzuć zmiany commita dla tego pliku o: otwórz plik - ▲: poprzednia linia - ▼: następna linia - ◄: poprzedni kawałek - ►: następny kawałek - ctrl+o: copy the selected text to the clipboard - space: add/remove line(s) to patch - v: toggle drag select - V: toggle drag select - a: toggle select hunk -- -## Główne Panel (Poczekalnia) - -
- esc: wróć do panelu plików - space: toggle line staged / unstaged - d: delete change (git reset) - tab: switch to other panel - o: otwórz plik - ▲: poprzednia linia - ▼: następna linia - ◄: poprzedni kawałek - ►: następny kawałek - ctrl+o: copy the selected text to the clipboard e: edytuj plik - o: otwórz plik - v: toggle drag select - V: toggle drag select - a: toggle select hunk - c: Zatwierdź zmiany - w: zatwierdź zmiany bez skryptu pre-commit - C: Zatwierdź zmiany używając edytora -- -## Menu Panel - -
- esc: close menu + space: toggle file included in patch + a: toggle all files included in patch + enter: enter file to add selected lines to the patch (or toggle directory collapsed) + `: toggle file tree view## Schowek Panel (Schowek) diff --git a/docs/keybindings/Keybindings_zh.md b/docs/keybindings/Keybindings_zh.md index 12b9a90f7..d477edb40 100644 --- a/docs/keybindings/Keybindings_zh.md +++ b/docs/keybindings/Keybindings_zh.md @@ -41,6 +41,71 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct [: 上一个标签 +## Extras 面板 + +
+ @: 打开命令日志菜单 ++ +## 主要 面板 (合并中) + +
+ esc: 返回文件面板 + M: 打开合并工具 + space: 选中区块 + b: 选中所有区块 + ◄: 选择上一个冲突 + ►: 选择下一个冲突 + ▲: 选择顶部块 + ▼: 选择底部块 + z: 撤销 ++ +## 主要 面板 (构建补丁中) + +
+ esc: 退出逐行模式 + o: 打开文件 + ▲: 选择上一行 + ▼: 选择下一行 + ◄: 选择上一个区块 + ►: 选择下一个区块 + ctrl+o: copy the selected text to the clipboard + space: 添加/移除 行到补丁 + v: 切换拖动选择 + V: 切换拖动选择 + a: 切换选择区块 ++ +## 主要 面板 (正在暂存) + +
+ esc: 返回文件面板 + space: 切换行暂存状态 + d: 取消变更 (git reset) + tab: 切换到其他面板 + o: 打开文件 + ▲: 选择上一行 + ▼: 选择下一行 + ◄: 选择上一个区块 + ►: 选择下一个区块 + ctrl+o: copy the selected text to the clipboard + e: 编辑文件 + v: 切换拖动选择 + V: 切换拖动选择 + a: 切换选择区块 + c: 提交更改 + w: 提交更改而无需预先提交钩子 + C: 提交更改(使用编辑器编辑提交信息) ++ +## 主要 面板 (正常) + +
+ mouse wheel down: 向下滚动 (fn+up) + mouse wheel up: 向上滚动 (fn+down) ++ ## 分支 面板 (分支标签)
@@ -62,29 +127,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct enter: 查看提交-## 分支 面板 (远程分支(在远程页面中)) - -
- space: 检出 - n: 新分支 - M: 合并到当前检出的分支 - r: 将已检出的分支变基到该分支 - d: 删除分支 - u: 设置为检出分支的上游 - esc: 返回远程仓库列表 - g: 查看重置选项 - enter: 查看提交 -- -## 分支 面板 (远程页面) - -
- f: 抓取远程仓库 - n: 添加新的远程仓库 - d: 删除远程 - e: 编辑远程仓库 -- ## 分支 面板 (子提交)
@@ -109,23 +151,39 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct enter: 查看提交-## 提交文件 面板 +## 分支 面板 (远程分支(在远程页面中))
- ctrl+o: 将提交的文件名复制到剪贴板 + space: 检出 + n: 新分支 + M: 合并到当前检出的分支 + r: 将已检出的分支变基到该分支 + d: 删除分支 + u: 设置为检出分支的上游 + esc: 返回远程仓库列表 + g: 查看重置选项 + enter: 查看提交-## 提交文件 面板 (提交文件) +## 分支 面板 (远程页面)
- c: 检出文件 - d: 放弃对此文件的提交更改 - o: 打开文件 - e: 编辑文件 - space: 补丁中包含的切换文件 - a: toggle all files included in patch - enter: 输入文件以将所选行添加到补丁中(或切换目录折叠) - `: 切换文件树视图 + f: 抓取远程仓库 + n: 添加新的远程仓库 + d: 删除远程 + e: 编辑远程仓库 ++ +## 提交 面板 (Reflog) + +
+ ctrl+o: 将提交的 SHA 复制到剪贴板 + space: 检出提交 + g: 查看重置选项 + c: 复制提交(拣选) + C: 复制提交范围(拣选) + ctrl+r: 重置已拣选(复制)的提交 + enter: 查看提交的文件## 提交 面板 (提交) @@ -160,22 +218,36 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct enter: 查看提交的文件 -## 提交 面板 (Reflog) +## 提交文件 面板
- ctrl+o: 将提交的 SHA 复制到剪贴板 - space: 检出提交 - g: 查看重置选项 - c: 复制提交(拣选) - C: 复制提交范围(拣选) - ctrl+r: 重置已拣选(复制)的提交 - enter: 查看提交的文件 + ctrl+o: 将提交的文件名复制到剪贴板-## Extras 面板 +## 提交文件 面板 (提交文件)
- @: 打开命令日志菜单 + c: 检出文件 + d: 放弃对此文件的提交更改 + o: 打开文件 + e: 编辑文件 + space: 补丁中包含的切换文件 + a: toggle all files included in patch + enter: 输入文件以将所选行添加到补丁中(或切换目录折叠) + `: 切换文件树视图 ++ +## 文件 面板 (子模块) + +
+ ctrl+o: 将子模块名称复制到剪贴板 + enter: 输入子模块 + d: 删除子模块 + u: 更新子模块 + n: 添加新的子模块 + e: 更新子模块 URL + i: 初始化子模块 + b: 查看批量子模块选项## 文件 面板 (文件) @@ -205,77 +277,14 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct f: 抓取 -## 文件 面板 (子模块) +## 状态 面板 (状态)
- ctrl+o: 将子模块名称复制到剪贴板 - enter: 输入子模块 - d: 删除子模块 - u: 更新子模块 - n: 添加新的子模块 - e: 更新子模块 URL - i: 初始化子模块 - b: 查看批量子模块选项 -- -## 主要 面板 (合并中) - -
- esc: 返回文件面板 - M: 打开合并工具 - space: 选中区块 - b: 选中所有区块 - ◄: 选择上一个冲突 - ►: 选择下一个冲突 - ▲: 选择顶部块 - ▼: 选择底部块 - z: 撤销 -- -## 主要 面板 (正常) - -
- mouse wheel down: 向下滚动 (fn+up) - mouse wheel up: 向上滚动 (fn+down) -- -## 主要 面板 (构建补丁中) - -
- esc: 退出逐行模式 - o: 打开文件 - ▲: 选择上一行 - ▼: 选择下一行 - ◄: 选择上一个区块 - ►: 选择下一个区块 - ctrl+o: copy the selected text to the clipboard - space: 添加/移除 行到补丁 - v: 切换拖动选择 - V: 切换拖动选择 - a: 切换选择区块 -- -## 主要 面板 (正在暂存) - -
- esc: 返回文件面板 - space: 切换行暂存状态 - d: 取消变更 (git reset) - tab: 切换到其他面板 - o: 打开文件 - ▲: 选择上一行 - ▼: 选择下一行 - ◄: 选择上一个区块 - ►: 选择下一个区块 - ctrl+o: copy the selected text to the clipboard - e: 编辑文件 - o: 打开文件 - v: 切换拖动选择 - V: 切换拖动选择 - a: 切换选择区块 - c: 提交更改 - w: 提交更改而无需预先提交钩子 - C: 提交更改(使用编辑器编辑提交信息) + e: 编辑配置文件 + o: 打开配置文件 + u: 检查更新 + enter: 切换到最近的仓库 + a: 显示所有分支的日志## 菜单 面板 @@ -293,13 +302,3 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct n: 新分支 enter: 查看提交的文件 - -## 状态 面板 (状态) - -
- e: 编辑配置文件 - o: 打开配置文件 - u: 检查更新 - enter: 切换到最近的仓库 - a: 显示所有分支的日志 -diff --git a/pkg/cheatsheet/generate.go b/pkg/cheatsheet/generate.go index 04d8d3fd5..d20a0c71a 100644 --- a/pkg/cheatsheet/generate.go +++ b/pkg/cheatsheet/generate.go @@ -21,6 +21,8 @@ import ( "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/integration" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/samber/lo" ) type bindingSection struct { @@ -28,6 +30,17 @@ type bindingSection struct { bindings []*types.Binding } +type header struct { + // priority decides the order of the headers in the cheatsheet (lower means higher) + priority int + title string +} + +type headerWithBindings struct { + header header + bindings []*types.Binding +} + func CommandToRun() string { return "go run scripts/cheatsheet/main.go generate" } @@ -49,7 +62,8 @@ func generateAtDir(cheatsheetDir string) { panic(err) } - bindingSections := getBindingSections(mApp) + bindings := mApp.Gui.GetCheatsheetKeybindings() + bindingSections := getBindingSections(bindings, mApp.Tr) content := formatSections(mApp.Tr, bindingSections) content = fmt.Sprintf("_This file is auto-generated. To update, make the changes in the "+ "pkg/i18n directory and then run `%s` from the project root._\n\n%s", CommandToRun(), content) @@ -68,9 +82,7 @@ func writeString(file *os.File, str string) { } } -func localisedTitle(mApp *app.App, str string) string { - tr := mApp.Tr - +func localisedTitle(tr *i18n.TranslationSet, str string) string { contextTitleMap := map[string]string{ "global": tr.GlobalTitle, "navigation": tr.NavigationTitle, @@ -110,142 +122,66 @@ func localisedTitle(mApp *app.App, str string) string { return title } -func formatTitle(title string) string { - return fmt.Sprintf("\n## %s\n\n", title) -} - -func formatBinding(binding *types.Binding) string { - if binding.Alternative != "" { - return fmt.Sprintf(" %s: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, binding.Alternative) - } - return fmt.Sprintf(" %s: %s\n", gui.GetKeyDisplay(binding.Key), binding.Description) -} - -func getBindingSections(mApp *app.App) []*bindingSection { - bindingSections := []*bindingSection{} - - bindings := mApp.Gui.GetCheatsheetKeybindings() - - type contextAndViewType struct { - subtitle string - title string - } - - contextAndViewBindingMap := map[contextAndViewType][]*types.Binding{} - -outer: - for _, binding := range bindings { - if binding.Tag == "navigation" { - key := contextAndViewType{subtitle: "", title: "navigation"} - existing := contextAndViewBindingMap[key] - if existing == nil { - contextAndViewBindingMap[key] = []*types.Binding{binding} - } else { - if !slices.Some(contextAndViewBindingMap[key], func(navBinding *types.Binding) bool { - return navBinding.Description == binding.Description - }) { - contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding) - } - } - - continue outer - } - - contexts := []string{} - if len(binding.Contexts) == 0 { - contexts = append(contexts, "") - } else { - contexts = append(contexts, binding.Contexts...) - } - - for _, context := range contexts { - key := contextAndViewType{subtitle: context, title: binding.ViewName} - existing := contextAndViewBindingMap[key] - if existing == nil { - contextAndViewBindingMap[key] = []*types.Binding{binding} - } else { - contextAndViewBindingMap[key] = append(contextAndViewBindingMap[key], binding) - } - } - } - - type groupedBindingsType struct { - contextAndView contextAndViewType - bindings []*types.Binding - } - - groupedBindings := maps.MapToSlice( - contextAndViewBindingMap, - func(contextAndView contextAndViewType, contextBindings []*types.Binding) groupedBindingsType { - return groupedBindingsType{contextAndView: contextAndView, bindings: contextBindings} - }, - ) - - slices.SortFunc(groupedBindings, func(a, b groupedBindingsType) bool { - first := a.contextAndView - second := b.contextAndView - if first.title == "" { - return true - } - if second.title == "" { - return false - } - if first.title == "navigation" { - return true - } - if second.title == "navigation" { - return false - } - return first.title < second.title || (first.title == second.title && first.subtitle < second.subtitle) +func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection { + bindingsToDisplay := slices.Filter(bindings, func(binding *types.Binding) bool { + return binding.Description != "" || binding.Alternative != "" }) - for _, group := range groupedBindings { - contextAndView := group.contextAndView - contextBindings := group.bindings - mApp.Log.Info("viewname: " + contextAndView.title + ", context: " + contextAndView.subtitle) - viewName := contextAndView.title - if viewName == "" { - viewName = "global" - } - translatedView := localisedTitle(mApp, viewName) - var title string - if contextAndView.subtitle == "" { - addendum := " " + mApp.Tr.Panel - if viewName == "global" || viewName == "navigation" { - addendum = "" - } - title = fmt.Sprintf("%s%s", translatedView, addendum) - } else { - translatedContextName := localisedTitle(mApp, contextAndView.subtitle) - title = fmt.Sprintf("%s %s (%s)", translatedView, mApp.Tr.Panel, translatedContextName) - } + bindingsByHeader := utils.MuiltiGroupBy(bindingsToDisplay, func(binding *types.Binding) []header { + return getHeaders(binding, tr) + }) - for _, binding := range contextBindings { - bindingSections = addBinding(title, bindingSections, binding) - } - } + bindingGroups := maps.MapToSlice(bindingsByHeader, func(header header, hBindings []*types.Binding) headerWithBindings { + uniqBindings := lo.UniqBy(hBindings, func(binding *types.Binding) string { + return binding.Description + gui.GetKeyDisplay(binding.Key) + }) - return bindingSections + return headerWithBindings{ + header: header, + bindings: uniqBindings, + } + }) + + slices.SortFunc(bindingGroups, func(a, b headerWithBindings) bool { + if a.header.priority != b.header.priority { + return a.header.priority > b.header.priority + } + return a.header.title < b.header.title + }) + + return slices.Map(bindingGroups, func(hb headerWithBindings) *bindingSection { + return &bindingSection{ + title: hb.header.title, + bindings: hb.bindings, + } + }) } -func addBinding(title string, bindingSections []*bindingSection, binding *types.Binding) []*bindingSection { - if binding.Description == "" && binding.Alternative == "" { - return bindingSections +// a binding may belong to multiple headers if it is applicable to multiple contexts, +// for example the copy-to-clipboard binding. +func getHeaders(binding *types.Binding, tr *i18n.TranslationSet) []header { + if binding.Tag == "navigation" { + return []header{{priority: 2, title: localisedTitle(tr, "navigation")}} } - for _, section := range bindingSections { - if title == section.title { - section.bindings = append(section.bindings, binding) - return bindingSections - } + if binding.ViewName == "" { + return []header{{priority: 3, title: localisedTitle(tr, "global")}} } - section := &bindingSection{ - title: title, - bindings: []*types.Binding{binding}, + if len(binding.Contexts) == 0 { + translatedView := localisedTitle(tr, binding.ViewName) + title := fmt.Sprintf("%s %s", translatedView, tr.Panel) + + return []header{{priority: 1, title: title}} } - return append(bindingSections, section) + return slices.Map(binding.Contexts, func(context string) header { + translatedView := localisedTitle(tr, binding.ViewName) + translatedContextName := localisedTitle(tr, context) + title := fmt.Sprintf("%s %s (%s)", translatedView, tr.Panel, translatedContextName) + + return header{priority: 1, title: title} + }) } func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string { @@ -262,3 +198,14 @@ func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) return content } + +func formatTitle(title string) string { + return fmt.Sprintf("\n## %s\n\n", title) +} + +func formatBinding(binding *types.Binding) string { + if binding.Alternative != "" { + return fmt.Sprintf(" %s: %s (%s)\n", gui.GetKeyDisplay(binding.Key), binding.Description, binding.Alternative) + } + return fmt.Sprintf(" %s: %s\n", gui.GetKeyDisplay(binding.Key), binding.Description) +} diff --git a/pkg/cheatsheet/generate_test.go b/pkg/cheatsheet/generate_test.go new file mode 100644 index 000000000..94b571454 --- /dev/null +++ b/pkg/cheatsheet/generate_test.go @@ -0,0 +1,281 @@ +package cheatsheet + +import ( + "testing" + + "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/i18n" + "github.com/stretchr/testify/assert" +) + +func TestGetBindingSections(t *testing.T) { + tr := i18n.EnglishTranslationSet() + + tests := []struct { + testName string + bindings []*types.Binding + expected []*bindingSection + }{ + { + testName: "no bindings", + bindings: []*types.Binding{}, + expected: []*bindingSection{}, + }, + { + testName: "one binding", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + }, + }, + expected: []*bindingSection{ + { + title: "Files Panel", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + }, + }, + }, + }, + }, + { + testName: "one binding with context", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + Contexts: []string{"submodules"}, + }, + }, + expected: []*bindingSection{ + { + title: "Files Panel (Submodules)", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + Contexts: []string{"submodules"}, + }, + }, + }, + }, + }, + { + testName: "global binding", + bindings: []*types.Binding{ + { + ViewName: "", + Description: "quit", + }, + }, + expected: []*bindingSection{ + { + title: "Global Keybindings", + bindings: []*types.Binding{ + { + ViewName: "", + Description: "quit", + }, + }, + }, + }, + }, + { + testName: "grouped bindings", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + Contexts: []string{"files"}, + }, + { + ViewName: "files", + Description: "unstage file", + Contexts: []string{"files"}, + }, + { + ViewName: "files", + Description: "drop submodule", + Contexts: []string{"submodules"}, + }, + { + ViewName: "commits", + Description: "revert commit", + }, + }, + expected: []*bindingSection{ + { + title: "Commits Panel", + bindings: []*types.Binding{ + { + ViewName: "commits", + Description: "revert commit", + }, + }, + }, + { + title: "Files Panel (Files)", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + Contexts: []string{"files"}, + }, + { + ViewName: "files", + Description: "unstage file", + Contexts: []string{"files"}, + }, + }, + }, + { + title: "Files Panel (Submodules)", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "drop submodule", + Contexts: []string{"submodules"}, + }, + }, + }, + }, + }, + { + testName: "with navigation bindings", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + }, + { + ViewName: "files", + Description: "unstage file", + }, + { + ViewName: "files", + Description: "scroll", + Tag: "navigation", + }, + { + ViewName: "commits", + Description: "revert commit", + }, + }, + expected: []*bindingSection{ + { + title: "List Panel Navigation", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "scroll", + Tag: "navigation", + }, + }, + }, + { + title: "Commits Panel", + bindings: []*types.Binding{ + { + ViewName: "commits", + Description: "revert commit", + }, + }, + }, + { + title: "Files Panel", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + }, + { + ViewName: "files", + Description: "unstage file", + }, + }, + }, + }, + }, + { + testName: "with duplicate navigation bindings", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + }, + { + ViewName: "files", + Description: "unstage file", + }, + { + ViewName: "files", + Description: "scroll", + Tag: "navigation", + }, + { + ViewName: "commits", + Description: "revert commit", + }, + { + ViewName: "commits", + Description: "scroll", + Tag: "navigation", + }, + { + ViewName: "commits", + Description: "page up", + Tag: "navigation", + }, + }, + expected: []*bindingSection{ + { + title: "List Panel Navigation", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "scroll", + Tag: "navigation", + }, + { + ViewName: "commits", + Description: "page up", + Tag: "navigation", + }, + }, + }, + { + title: "Commits Panel", + bindings: []*types.Binding{ + { + ViewName: "commits", + Description: "revert commit", + }, + }, + }, + { + title: "Files Panel", + bindings: []*types.Binding{ + { + ViewName: "files", + Description: "stage file", + }, + { + ViewName: "files", + Description: "unstage file", + }, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.testName, func(t *testing.T) { + actual := getBindingSections(test.bindings, &tr) + assert.EqualValues(t, test.expected, actual) + }) + } +} diff --git a/pkg/utils/slice.go b/pkg/utils/slice.go index 6971c9367..2281d8a73 100644 --- a/pkg/utils/slice.go +++ b/pkg/utils/slice.go @@ -76,3 +76,19 @@ func LimitStr(value string, limit int) string { } return value } + +// Similar to a regular GroupBy, except that each item can be grouped under multiple keys, +// so the callback returns a slice of keys instead of just one key. +func MuiltiGroupBy[T any, K comparable](slice []T, f func(T) []K) map[K][]T { + result := map[K][]T{} + for _, item := range slice { + for _, key := range f(item) { + if _, ok := result[key]; !ok { + result[key] = []T{item} + } else { + result[key] = append(result[key], item) + } + } + } + return result +}