make keybinds configurable and consolidate
This commit is contained in:
358
tui.go
358
tui.go
@@ -135,6 +135,7 @@ type App struct {
|
||||
dashboardReturn screen
|
||||
compactReviews bool
|
||||
editorMode string
|
||||
keybindings KeyBindings
|
||||
readState *readStateStore
|
||||
knownThreads map[string]bool
|
||||
knownComments map[string]bool
|
||||
@@ -153,6 +154,7 @@ type AppSettings struct {
|
||||
DashboardMode string
|
||||
CompactReviews bool
|
||||
EditorMode string
|
||||
KeyBindings KeyBindings
|
||||
ReadState *readStateStore
|
||||
}
|
||||
|
||||
@@ -166,6 +168,7 @@ func defaultAppSettings() AppSettings {
|
||||
DashboardMode: "hotkey",
|
||||
CompactReviews: true,
|
||||
EditorMode: "vim",
|
||||
KeyBindings: defaultKeyBindings(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,6 +198,7 @@ func NewAppWithSettings(
|
||||
dashboardMode: settings.DashboardMode, dashboardReturn: prScreen,
|
||||
compactReviews: settings.CompactReviews,
|
||||
editorMode: settings.EditorMode,
|
||||
keybindings: settings.KeyBindings,
|
||||
readState: state,
|
||||
knownThreads: make(map[string]bool), knownComments: make(map[string]bool),
|
||||
initializedPRs: make(map[string]bool), unreadThreads: make(map[string]bool),
|
||||
@@ -328,10 +332,11 @@ func (m App) writeActionUnavailable(action string, thread *ReviewThread) string
|
||||
}
|
||||
|
||||
func (m App) updateWriteInput(key tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
k := key.String()
|
||||
if k == "ctrl+c" {
|
||||
if key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(key.String(), m.keybindings.General.Quit) {
|
||||
return m, tea.Quit
|
||||
}
|
||||
k := m.keybindings.canonicalWriteKey(key.String())
|
||||
switch m.writeMode {
|
||||
case writeReply:
|
||||
switch k {
|
||||
@@ -611,10 +616,8 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, nil
|
||||
}
|
||||
k := key.String()
|
||||
if m.writeMode != writeNone {
|
||||
return m.updateWriteInput(key)
|
||||
}
|
||||
if m.helpVisible {
|
||||
k = m.keybindings.canonicalHelpKey(k)
|
||||
switch k {
|
||||
case "ctrl+c":
|
||||
return m, tea.Quit
|
||||
@@ -635,7 +638,25 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
if m.writeMode == writePREdit || m.writeMode == writePREditConfirm {
|
||||
editor := m.prEditEditors[m.prEditField]
|
||||
helpOutsideTextInput := key.Type != tea.KeyRunes && key.Type != tea.KeySpace
|
||||
helpInVimNormalMode := editor.Modal && editor.Mode == textEditorNormal
|
||||
if keyMatches(k, m.keybindings.General.Help) &&
|
||||
(helpOutsideTextInput || helpInVimNormalMode) {
|
||||
m.helpVisible, m.helpScroll = true, 0
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
if m.writeMode != writeNone {
|
||||
return m.updateWriteInput(key)
|
||||
}
|
||||
if m.searching {
|
||||
if key.Type != tea.KeyRunes && key.Type != tea.KeySpace &&
|
||||
keyMatches(k, m.keybindings.General.Quit) {
|
||||
return m, tea.Quit
|
||||
}
|
||||
k = m.keybindings.canonicalSearchKey(k)
|
||||
switch k {
|
||||
case "ctrl+c":
|
||||
return m, tea.Quit
|
||||
@@ -667,6 +688,8 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
rawKey := k
|
||||
k = m.keybindings.canonicalMainKey(k, m.screen)
|
||||
if k == "ctrl+c" || k == "q" {
|
||||
return m, tea.Quit
|
||||
}
|
||||
@@ -676,7 +699,8 @@ func (m App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
if m.pendingZ {
|
||||
m.pendingZ = false
|
||||
if k == "a" && m.screen == threadScreen && len(m.details.Threads) > 0 {
|
||||
if keyMatches(rawKey, m.keybindings.Threads.FoldToggle) &&
|
||||
m.screen == threadScreen && len(m.details.Threads) > 0 {
|
||||
thread := m.details.Threads[m.threadIndex]
|
||||
m.folded[thread.ID] = !m.folded[thread.ID]
|
||||
m.scroll = 0
|
||||
@@ -1227,15 +1251,15 @@ func (m App) View() string {
|
||||
if m.width == 0 {
|
||||
return "Loading…"
|
||||
}
|
||||
if m.helpVisible {
|
||||
return m.viewHelp()
|
||||
}
|
||||
if m.writeMode != writeNone && m.writeMode != writeReply {
|
||||
if m.writeMode == writePREdit {
|
||||
return m.viewDashboard()
|
||||
}
|
||||
return m.viewWritePopup()
|
||||
}
|
||||
if m.helpVisible {
|
||||
return m.viewHelp()
|
||||
}
|
||||
if m.screen == prScreen {
|
||||
return m.viewPRs()
|
||||
}
|
||||
@@ -1267,11 +1291,20 @@ func (m App) viewWritePopup() string {
|
||||
if m.err != nil {
|
||||
lines = append(lines, "", badStyle.Render(m.err.Error()))
|
||||
}
|
||||
lines = append(lines, "", dimStyle.Render("enter newline • ctrl-s review • esc cancel"))
|
||||
lines = append(lines, "", dimStyle.Render(fmt.Sprintf(
|
||||
"%s newline • %s review • %s cancel",
|
||||
primaryKeyLabel(m.keybindings.Input.Newline),
|
||||
primaryKeyLabel(m.keybindings.Input.Submit),
|
||||
primaryKeyLabel(m.keybindings.Input.Cancel),
|
||||
)))
|
||||
case writeReplyConfirm:
|
||||
lines = []string{titleStyle.Render("Submit this reply to " + location + "?"), ""}
|
||||
lines = append(lines, renderCommentMarkdown(m.replyDraft, width-2)...)
|
||||
lines = append(lines, "", warnStyle.Render("y submit • n/esc continue editing"))
|
||||
lines = append(lines, "", warnStyle.Render(fmt.Sprintf(
|
||||
"%s submit • %s continue editing",
|
||||
primaryKeyLabel(m.keybindings.General.Confirm),
|
||||
primaryCombinedKeyLabel(m.keybindings.General.Reject, m.keybindings.Input.Cancel),
|
||||
)))
|
||||
case writeReplyBusy:
|
||||
lines = []string{titleStyle.Render("Submitting reply…"), "", dimStyle.Render(location)}
|
||||
case writeResolveConfirm:
|
||||
@@ -1282,7 +1315,11 @@ func (m App) viewWritePopup() string {
|
||||
lines = []string{
|
||||
titleStyle.Render(strings.ToUpper(action[:1]) + action[1:] + " " + location + "?"),
|
||||
"",
|
||||
warnStyle.Render("y confirm • n/esc cancel"),
|
||||
warnStyle.Render(fmt.Sprintf(
|
||||
"%s confirm • %s cancel",
|
||||
primaryKeyLabel(m.keybindings.General.Confirm),
|
||||
primaryCombinedKeyLabel(m.keybindings.General.Reject, m.keybindings.Input.Cancel),
|
||||
)),
|
||||
}
|
||||
case writeResolveBusy:
|
||||
action := "Resolving"
|
||||
@@ -1322,21 +1359,94 @@ type helpBinding struct {
|
||||
}
|
||||
|
||||
func (m App) helpBindings() []helpBinding {
|
||||
if m.writeMode == writePREdit || m.writeMode == writePREditConfirm {
|
||||
bindings := []helpBinding{
|
||||
{combinedKeyLabel(m.keybindings.Input.NextField, m.keybindings.Input.PreviousField), "Move to the next / previous field"},
|
||||
{keyLabel(m.keybindings.Input.Submit), "Review pull request metadata changes"},
|
||||
{keyLabel(m.keybindings.Input.Cancel), "Return to Normal mode or cancel the editor"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.PageDown, m.keybindings.Navigation.PageUp), "Move through the description by half a page"},
|
||||
{combinedKeyLabel(m.keybindings.Input.PreviousCompletion, m.keybindings.Input.NextCompletion), "Select the previous / next target-branch completion"},
|
||||
{combinedKeyLabel(m.keybindings.Input.NextField, m.keybindings.Input.Newline), "Complete the selected target branch"},
|
||||
}
|
||||
if m.prEditEditors[prEditBodyField].Modal {
|
||||
bindings = append(bindings,
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Navigation.Left, m.keybindings.Navigation.Down,
|
||||
m.keybindings.Navigation.Up, m.keybindings.Navigation.Right,
|
||||
), "Move left / down / up / right in Normal or Visual mode"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.WordForward, m.keybindings.Vim.WORDForward,
|
||||
m.keybindings.Vim.WordBackward, m.keybindings.Vim.WORDBackward,
|
||||
m.keybindings.Vim.WordEnd, m.keybindings.Vim.WORDEnd,
|
||||
), "Move by words or WORDs"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.LineStart, m.keybindings.Vim.FirstNonBlank,
|
||||
m.keybindings.Vim.LineEnd,
|
||||
), "Move to the line start, first non-blank, or line end"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.GoPrefix, m.keybindings.Navigation.Last,
|
||||
), "Move to the start / end of the description"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.Insert, m.keybindings.Vim.Append,
|
||||
m.keybindings.Vim.InsertLineStart, m.keybindings.Vim.AppendLineEnd,
|
||||
m.keybindings.Vim.ReplaceCharacter,
|
||||
), "Enter Insert mode"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.OpenBelow, m.keybindings.Vim.OpenAbove,
|
||||
), "Open a line below / above"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.Visual, m.keybindings.Vim.VisualLine,
|
||||
), "Start character-wise / line-wise Visual mode"},
|
||||
helpBinding{keyLabel(m.keybindings.Vim.SelectionOtherEnd), "Move to the other end of a Visual selection"},
|
||||
helpBinding{keyLabel(m.keybindings.Vim.Yank), "Copy the Visual selection to the system clipboard"},
|
||||
helpBinding{keyLabel(m.keybindings.Vim.Paste), "Paste from the system clipboard"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.Delete, m.keybindings.Vim.DeleteBefore,
|
||||
), "Delete the selection or text at / before the cursor"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.FindForward, m.keybindings.Vim.FindBackward,
|
||||
m.keybindings.Vim.TillForward, m.keybindings.Vim.TillBackward,
|
||||
), "Find or move until a character on the current visual line"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Vim.RepeatFind, m.keybindings.Vim.RepeatFindReverse,
|
||||
), "Repeat the last character find forward / backward"},
|
||||
)
|
||||
} else {
|
||||
bindings = append(bindings,
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Navigation.Left, m.keybindings.Navigation.Down,
|
||||
m.keybindings.Navigation.Up, m.keybindings.Navigation.Right,
|
||||
), "Move the description cursor"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Input.LineStart, m.keybindings.Input.LineEnd,
|
||||
), "Move to the start / end of the current visual line"},
|
||||
helpBinding{keyLabel(m.keybindings.Input.Newline), "Insert a newline"},
|
||||
helpBinding{combinedKeyLabel(
|
||||
m.keybindings.Input.DeleteBackward, m.keybindings.Input.DeleteForward,
|
||||
), "Delete text before / at the cursor"},
|
||||
)
|
||||
}
|
||||
bindings = append(bindings,
|
||||
helpBinding{keyLabel(m.keybindings.General.Help), "Close this help"},
|
||||
helpBinding{keyLabel(m.keybindings.General.Quit), "Quit"},
|
||||
)
|
||||
return bindings
|
||||
}
|
||||
if m.screen == prScreen {
|
||||
openAction := "Open pull request dashboard"
|
||||
if m.dashboardMode == "hotkey" {
|
||||
openAction = "Open review threads"
|
||||
}
|
||||
return []helpBinding{
|
||||
{"j / ↓", "Next pull request"},
|
||||
{"k / ↑", "Previous pull request"},
|
||||
{"g / G", "First / last pull request"},
|
||||
{"ctrl-d / ctrl-u", "Page down / up"},
|
||||
{"enter / l", openAction},
|
||||
{"d", "Open pull request dashboard"},
|
||||
{"r", "Refresh now"},
|
||||
{"?", "Close this help"},
|
||||
{"q / ctrl-c", "Quit"},
|
||||
{keyLabel(m.keybindings.Navigation.Down), "Next pull request"},
|
||||
{keyLabel(m.keybindings.Navigation.Up), "Previous pull request"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.First, m.keybindings.Navigation.Last), "First / last pull request"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.PageDown, m.keybindings.Navigation.PageUp), "Page down / up"},
|
||||
{keyLabel(m.keybindings.Views.Open), openAction},
|
||||
{keyLabel(m.keybindings.Views.Dashboard), "Open pull request dashboard"},
|
||||
{keyLabel(m.keybindings.General.Refresh), "Refresh now"},
|
||||
{keyLabel(m.keybindings.General.Help), "Close this help"},
|
||||
{keyLabel(m.keybindings.General.Quit), "Quit"},
|
||||
}
|
||||
}
|
||||
if m.screen == dashboardScreen {
|
||||
@@ -1345,16 +1455,16 @@ func (m App) helpBindings() []helpBinding {
|
||||
backAction = "Return to review threads"
|
||||
}
|
||||
return []helpBinding{
|
||||
{"j / ↓", "Scroll description down"},
|
||||
{"k / ↑", "Scroll description up"},
|
||||
{"g / G", "Top / bottom"},
|
||||
{"ctrl-d / ctrl-u", "Page down / up"},
|
||||
{"e", "Edit title, target branch, and description"},
|
||||
{"enter / l", "Open review threads"},
|
||||
{"b / esc", backAction},
|
||||
{"r", "Refresh now"},
|
||||
{"?", "Close this help"},
|
||||
{"q / ctrl-c", "Quit"},
|
||||
{keyLabel(m.keybindings.Navigation.Down), "Scroll description down"},
|
||||
{keyLabel(m.keybindings.Navigation.Up), "Scroll description up"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.First, m.keybindings.Navigation.Last), "Top / bottom"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.PageDown, m.keybindings.Navigation.PageUp), "Page down / up"},
|
||||
{keyLabel(m.keybindings.Views.Edit), "Edit title, target branch, and description"},
|
||||
{keyLabel(m.keybindings.Views.Open), "Open review threads"},
|
||||
{keyLabel(m.keybindings.General.Back), backAction},
|
||||
{keyLabel(m.keybindings.General.Refresh), "Refresh now"},
|
||||
{keyLabel(m.keybindings.General.Help), "Close this help"},
|
||||
{keyLabel(m.keybindings.General.Quit), "Quit"},
|
||||
}
|
||||
}
|
||||
backAction := "Return to pull requests"
|
||||
@@ -1362,31 +1472,34 @@ func (m App) helpBindings() []helpBinding {
|
||||
backAction = "Return to PR dashboard"
|
||||
}
|
||||
bindings := []helpBinding{
|
||||
{"h / l", "Focus thread list / detail"},
|
||||
{"j / k", "Move or scroll focused pane"},
|
||||
{"↓ / ↑", "Move or scroll focused pane"},
|
||||
{"g / G", "First / last item"},
|
||||
{"ctrl-d / ctrl-u", "Page down / up"},
|
||||
{"tab", "Hide / reveal thread list"},
|
||||
{"/", "Fuzzy-search file paths and combine status:, author:, and updated:true filters"},
|
||||
{"F", "Clear the active thread filter and show every thread"},
|
||||
{"n / N", "Next / previous new update"},
|
||||
{"c", "Compose a reply to the selected thread"},
|
||||
{"R", "Resolve or unresolve the selected thread"},
|
||||
{"d", "Open pull request dashboard"},
|
||||
{"enter / za", "Fold / expand thread"},
|
||||
{"b / esc", backAction},
|
||||
{"r", "Refresh now"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.Left, m.keybindings.Navigation.Right), "Focus thread list / detail"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.Down, m.keybindings.Navigation.Up), "Move or scroll focused pane"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.First, m.keybindings.Navigation.Last), "First / last item"},
|
||||
{combinedKeyLabel(m.keybindings.Navigation.PageDown, m.keybindings.Navigation.PageUp), "Page down / up"},
|
||||
{keyLabel(m.keybindings.Views.ToggleList), "Hide / reveal thread list"},
|
||||
{keyLabel(m.keybindings.Threads.Search), "Fuzzy-search file paths and combine status:, author:, and updated:true filters"},
|
||||
{keyLabel(m.keybindings.Threads.ClearFilter), "Clear the active thread filter and show every thread"},
|
||||
{combinedKeyLabel(m.keybindings.Threads.NextUnread, m.keybindings.Threads.PreviousUnread), "Next / previous new update"},
|
||||
{keyLabel(m.keybindings.Threads.Reply), "Compose a reply to the selected thread"},
|
||||
{keyLabel(m.keybindings.Threads.Resolve), "Resolve or unresolve the selected thread"},
|
||||
{keyLabel(m.keybindings.Views.Dashboard), "Open pull request dashboard"},
|
||||
{combinedKeyLabel(
|
||||
m.keybindings.Threads.Toggle,
|
||||
[]string{sequenceKeyLabel(m.keybindings.Threads.FoldPrefix, m.keybindings.Threads.FoldToggle)},
|
||||
), "Fold / expand thread"},
|
||||
{keyLabel(m.keybindings.General.Back), backAction},
|
||||
{keyLabel(m.keybindings.General.Refresh), "Refresh now"},
|
||||
}
|
||||
bindings = append(bindings,
|
||||
helpBinding{"?", "Close this help"},
|
||||
helpBinding{"q / ctrl-c", "Quit"},
|
||||
helpBinding{keyLabel(m.keybindings.General.Help), "Close this help"},
|
||||
helpBinding{keyLabel(m.keybindings.General.Quit), "Quit"},
|
||||
)
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (m App) helpVisibleRows() int {
|
||||
return max(1, m.height-5)
|
||||
// Reserve rows for the popup border, title, title divider, and footer.
|
||||
return max(1, m.height-6)
|
||||
}
|
||||
|
||||
func (m App) helpMaxScroll() int {
|
||||
@@ -1401,25 +1514,45 @@ func (m App) viewHelp() string {
|
||||
end := min(len(rows), start+visibleRows)
|
||||
|
||||
title := "Pull request picker keys"
|
||||
if m.screen == dashboardScreen {
|
||||
if m.writeMode == writePREdit || m.writeMode == writePREditConfirm {
|
||||
title = "Pull request editor keys"
|
||||
} else if m.screen == dashboardScreen {
|
||||
title = "Pull request dashboard keys"
|
||||
} else if m.screen == threadScreen {
|
||||
title = "Review thread keys"
|
||||
}
|
||||
lines := []string{titleStyle.Render(title)}
|
||||
lines = append(lines, rows[start:end]...)
|
||||
contentLines := append([]string(nil), rows[start:end]...)
|
||||
if len(rows) > visibleRows {
|
||||
lines = append(lines, dimStyle.Render(fmt.Sprintf(
|
||||
"%d–%d of %d • j/k scroll • ?/esc/q close",
|
||||
contentLines = append(contentLines, dimStyle.Render(fmt.Sprintf(
|
||||
"%d–%d of %d • %s/%s scroll • %s close",
|
||||
start+1, end, len(rows),
|
||||
primaryKeyLabel(m.keybindings.Navigation.Down),
|
||||
primaryKeyLabel(m.keybindings.Navigation.Up),
|
||||
primaryKeyLabel(m.keybindings.General.Help)+"/"+
|
||||
primaryKeyLabel(m.keybindings.General.Back),
|
||||
)))
|
||||
} else {
|
||||
lines = append(lines, dimStyle.Render("?/esc/q close"))
|
||||
contentLines = append(contentLines, dimStyle.Render(
|
||||
primaryKeyLabel(m.keybindings.General.Help)+"/"+
|
||||
primaryKeyLabel(m.keybindings.General.Back)+" close",
|
||||
))
|
||||
}
|
||||
for i := range lines {
|
||||
lines[i] = ansi.Truncate(lines[i], contentWidth, "")
|
||||
for i := range contentLines {
|
||||
contentLines[i] = ansi.Truncate(contentLines[i], contentWidth, "")
|
||||
}
|
||||
popup := paneStyle(true).Width(contentWidth).Render(strings.Join(lines, "\n"))
|
||||
border := lipgloss.NewStyle().Foreground(paneActiveColor)
|
||||
boxLines := []string{
|
||||
border.Render("╭" + strings.Repeat("─", contentWidth) + "╮"),
|
||||
border.Render("│") + pad(titleStyle.Render(title), contentWidth) + border.Render("│"),
|
||||
border.Render("├" + strings.Repeat("─", contentWidth) + "┤"),
|
||||
}
|
||||
for _, line := range contentLines {
|
||||
boxLines = append(boxLines,
|
||||
border.Render("│")+pad(line, contentWidth)+border.Render("│"),
|
||||
)
|
||||
}
|
||||
boxLines = append(boxLines, border.Render("╰"+strings.Repeat("─", contentWidth)+"╯"))
|
||||
popup := strings.Join(boxLines, "\n")
|
||||
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, popup)
|
||||
}
|
||||
|
||||
@@ -1431,16 +1564,34 @@ func (m App) helpRows(contentWidth int) []string {
|
||||
keyWidth := min(17, max(8, contentWidth/3))
|
||||
actionWidth := max(1, contentWidth-keyWidth-1)
|
||||
var rows []string
|
||||
for _, binding := range m.helpBindings() {
|
||||
wrapped := ansi.Hardwrap(ansi.Wordwrap(binding.action, actionWidth, ""), actionWidth, false)
|
||||
actionLines := strings.Split(wrapped, "\n")
|
||||
for index, action := range actionLines {
|
||||
key := ""
|
||||
if index == 0 {
|
||||
key = binding.key
|
||||
bindings := m.helpBindings()
|
||||
for bindingIndex, binding := range bindings {
|
||||
wrappedKeys := ansi.Hardwrap(
|
||||
ansi.Wordwrap(binding.key, keyWidth, ""),
|
||||
keyWidth,
|
||||
false,
|
||||
)
|
||||
keyLines := strings.Split(wrappedKeys, "\n")
|
||||
wrappedAction := ansi.Hardwrap(
|
||||
ansi.Wordwrap(binding.action, actionWidth, ""),
|
||||
actionWidth,
|
||||
false,
|
||||
)
|
||||
actionLines := strings.Split(wrappedAction, "\n")
|
||||
lineCount := max(len(keyLines), len(actionLines))
|
||||
for lineIndex := range lineCount {
|
||||
key, action := "", ""
|
||||
if lineIndex < len(keyLines) {
|
||||
key = keyLines[lineIndex]
|
||||
}
|
||||
if lineIndex < len(actionLines) {
|
||||
action = actionLines[lineIndex]
|
||||
}
|
||||
rows = append(rows, titleStyle.Render(pad(key, keyWidth))+" "+action)
|
||||
}
|
||||
if bindingIndex < len(bindings)-1 {
|
||||
rows = append(rows, dimStyle.Render(strings.Repeat("─", contentWidth)))
|
||||
}
|
||||
}
|
||||
return rows
|
||||
}
|
||||
@@ -1512,9 +1663,22 @@ func (m App) viewPRs() string {
|
||||
}
|
||||
lines = append(lines, line)
|
||||
}
|
||||
footer := "? keys • j/k move • enter dashboard • q quit"
|
||||
footer := fmt.Sprintf(
|
||||
"%s keys • %s move • %s dashboard • %s quit",
|
||||
primaryKeyLabel(m.keybindings.General.Help),
|
||||
primaryCombinedKeyLabel(m.keybindings.Navigation.Down, m.keybindings.Navigation.Up),
|
||||
primaryKeyLabel(m.keybindings.Views.Open),
|
||||
primaryKeyLabel(m.keybindings.General.Quit),
|
||||
)
|
||||
if m.dashboardMode == "hotkey" {
|
||||
footer = "? keys • j/k move • enter threads • d dashboard • q quit"
|
||||
footer = fmt.Sprintf(
|
||||
"%s keys • %s move • %s threads • %s dashboard • %s quit",
|
||||
primaryKeyLabel(m.keybindings.General.Help),
|
||||
primaryCombinedKeyLabel(m.keybindings.Navigation.Down, m.keybindings.Navigation.Up),
|
||||
primaryKeyLabel(m.keybindings.Views.Open),
|
||||
primaryKeyLabel(m.keybindings.Views.Dashboard),
|
||||
primaryKeyLabel(m.keybindings.General.Quit),
|
||||
)
|
||||
}
|
||||
return m.frame(lines, footer)
|
||||
}
|
||||
@@ -1552,9 +1716,22 @@ func (m App) viewDashboard() string {
|
||||
maxScroll := max(0, len(lines)-viewportHeight)
|
||||
scroll := min(m.scroll, maxScroll)
|
||||
visible := lines[scroll:min(len(lines), scroll+viewportHeight)]
|
||||
footer := "? keys • j/k scroll • enter threads • b back • q quit"
|
||||
footer := fmt.Sprintf(
|
||||
"%s keys • %s scroll • %s threads • %s back • %s quit",
|
||||
primaryKeyLabel(m.keybindings.General.Help),
|
||||
primaryCombinedKeyLabel(m.keybindings.Navigation.Down, m.keybindings.Navigation.Up),
|
||||
primaryKeyLabel(m.keybindings.Views.Open),
|
||||
primaryKeyLabel(m.keybindings.General.Back),
|
||||
primaryKeyLabel(m.keybindings.General.Quit),
|
||||
)
|
||||
if m.writeMode == writePREdit {
|
||||
footer = "tab fields • ctrl-d/u page • v/V select • y copy • p paste • ctrl-s review • esc normal/cancel"
|
||||
footer = fmt.Sprintf(
|
||||
"%s keys • %s fields • %s review • %s normal/cancel",
|
||||
primaryKeyLabel(m.keybindings.General.Help),
|
||||
primaryCombinedKeyLabel(m.keybindings.Input.NextField, m.keybindings.Input.PreviousField),
|
||||
primaryKeyLabel(m.keybindings.Input.Submit),
|
||||
primaryKeyLabel(m.keybindings.Input.Cancel),
|
||||
)
|
||||
m.positionPREditHardwareCursor(scroll, viewportHeight)
|
||||
}
|
||||
view := m.frame(visible, footer)
|
||||
@@ -2010,11 +2187,30 @@ func (m App) viewThreads() string {
|
||||
right := m.threadDetail(rightWidth, contentHeight)
|
||||
body = lipgloss.JoinHorizontal(lipgloss.Top, left, " ", right)
|
||||
}
|
||||
help := "? keys • h/l focus • j/k move/scroll • c reply • R resolve • d dashboard • b back • q quit"
|
||||
help := fmt.Sprintf(
|
||||
"%s keys • %s focus • %s move/scroll • %s reply • %s resolve • %s dashboard • %s back • %s quit",
|
||||
primaryKeyLabel(m.keybindings.General.Help),
|
||||
primaryCombinedKeyLabel(m.keybindings.Navigation.Left, m.keybindings.Navigation.Right),
|
||||
primaryCombinedKeyLabel(m.keybindings.Navigation.Down, m.keybindings.Navigation.Up),
|
||||
primaryKeyLabel(m.keybindings.Threads.Reply),
|
||||
primaryKeyLabel(m.keybindings.Threads.Resolve),
|
||||
primaryKeyLabel(m.keybindings.Views.Dashboard),
|
||||
primaryKeyLabel(m.keybindings.General.Back),
|
||||
primaryKeyLabel(m.keybindings.General.Quit),
|
||||
)
|
||||
if m.searching {
|
||||
help = "path words • status:open • author:name • updated:true • enter apply • esc cancel"
|
||||
help = fmt.Sprintf(
|
||||
"path words • status:open • author:name • updated:true • %s apply • %s cancel",
|
||||
primaryKeyLabel(m.keybindings.Input.Newline),
|
||||
primaryKeyLabel(m.keybindings.Input.Cancel),
|
||||
)
|
||||
} else if m.writeMode == writeReply {
|
||||
help = "reply inline • enter newline • ctrl-s review • esc cancel"
|
||||
help = fmt.Sprintf(
|
||||
"reply inline • %s newline • %s review • %s cancel",
|
||||
primaryKeyLabel(m.keybindings.Input.Newline),
|
||||
primaryKeyLabel(m.keybindings.Input.Submit),
|
||||
primaryKeyLabel(m.keybindings.Input.Cancel),
|
||||
)
|
||||
}
|
||||
return m.frame(append(top, body), help)
|
||||
}
|
||||
@@ -2138,7 +2334,16 @@ func (m App) detailLines(width int) []detailLine {
|
||||
{anchor: "header", text: titleStyle.Render(fmt.Sprintf("[%d/%d] %s:%d", m.threadIndex+1, len(m.details.Threads), truncatePath(thread.Path, max(8, width-24)), thread.Line)) + " " + dimStyle.Render(status)},
|
||||
}
|
||||
if m.folded[thread.ID] {
|
||||
lines = append(lines, detailLine{}, detailLine{text: dimStyle.Render("Thread folded. Press za or enter to expand.")})
|
||||
lines = append(lines, detailLine{}, detailLine{text: dimStyle.Render(fmt.Sprintf(
|
||||
"Thread folded. Press %s to expand.",
|
||||
primaryCombinedKeyLabel(
|
||||
m.keybindings.Threads.Toggle,
|
||||
[]string{primarySequenceKeyLabel(
|
||||
m.keybindings.Threads.FoldPrefix,
|
||||
m.keybindings.Threads.FoldToggle,
|
||||
)},
|
||||
),
|
||||
))})
|
||||
} else {
|
||||
if len(thread.Comments) > 0 {
|
||||
lines = append(lines, detailLine{})
|
||||
@@ -2294,7 +2499,12 @@ func (m App) inlineReplyLines(width int) []detailLine {
|
||||
}
|
||||
lines = append(lines, detailLine{
|
||||
rail: rail,
|
||||
text: dimStyle.Render("enter newline • ctrl-s review • esc cancel"),
|
||||
text: dimStyle.Render(fmt.Sprintf(
|
||||
"%s newline • %s review • %s cancel",
|
||||
primaryKeyLabel(m.keybindings.Input.Newline),
|
||||
primaryKeyLabel(m.keybindings.Input.Submit),
|
||||
primaryKeyLabel(m.keybindings.Input.Cancel),
|
||||
)),
|
||||
})
|
||||
return lines
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user