mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-17 01:42:45 +02:00
Add prompt to the remote branch checkout menu (#3652)
- **PR Description** As a followup to [this discussion](https://github.com/jesseduffield/lazygit/pull/3388#issuecomment-2002308045), this PR adds a way to add a prompt text to menus. It is shown above the menu items, separated by a blank line. We use it to add a prompt to the remote branch checkout menu.
This commit is contained in:
@ -50,6 +50,8 @@ func NewMenuContext(
|
||||
type MenuViewModel struct {
|
||||
c *ContextCommon
|
||||
menuItems []*types.MenuItem
|
||||
prompt string
|
||||
promptLines []string
|
||||
columnAlignment []utils.Alignment
|
||||
*FilteredListViewModel[*types.MenuItem]
|
||||
}
|
||||
@ -73,6 +75,23 @@ func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem, columnAlignment
|
||||
self.columnAlignment = columnAlignment
|
||||
}
|
||||
|
||||
func (self *MenuViewModel) GetPrompt() string {
|
||||
return self.prompt
|
||||
}
|
||||
|
||||
func (self *MenuViewModel) SetPrompt(prompt string) {
|
||||
self.prompt = prompt
|
||||
self.promptLines = nil
|
||||
}
|
||||
|
||||
func (self *MenuViewModel) GetPromptLines() []string {
|
||||
return self.promptLines
|
||||
}
|
||||
|
||||
func (self *MenuViewModel) SetPromptLines(promptLines []string) {
|
||||
self.promptLines = promptLines
|
||||
}
|
||||
|
||||
// TODO: move into presentation package
|
||||
func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
|
||||
menuItems := self.FilteredListViewModel.GetItems()
|
||||
@ -94,14 +113,22 @@ func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
|
||||
}
|
||||
|
||||
func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
|
||||
result := []*NonModelItem{}
|
||||
result = append(result, lo.Map(self.promptLines, func(line string, _ int) *NonModelItem {
|
||||
return &NonModelItem{
|
||||
Index: 0,
|
||||
Column: 0,
|
||||
Content: line,
|
||||
}
|
||||
})...)
|
||||
|
||||
// Don't display section headers when we are filtering, and the filter mode
|
||||
// is fuzzy. The reason is that filtering changes the order of the items
|
||||
// (they are sorted by best match), so all the sections would be messed up.
|
||||
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig.Gui.UseFuzzySearch() {
|
||||
return []*NonModelItem{}
|
||||
return result
|
||||
}
|
||||
|
||||
result := []*NonModelItem{}
|
||||
menuItems := self.FilteredListViewModel.GetItems()
|
||||
var prevSection *types.MenuSection = nil
|
||||
for i, menuItem := range menuItems {
|
||||
|
@ -63,15 +63,20 @@ func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
|
||||
|
||||
// Temporary hack: we're just duplicating the logic in `gocui.lineWrap`
|
||||
func getMessageHeight(wrap bool, message string, width int) int {
|
||||
if !wrap {
|
||||
return len(strings.Split(message, "\n"))
|
||||
return len(wrapMessageToWidth(wrap, message, width))
|
||||
}
|
||||
|
||||
lineCount := 0
|
||||
func wrapMessageToWidth(wrap bool, message string, width int) []string {
|
||||
lines := strings.Split(message, "\n")
|
||||
if !wrap {
|
||||
return lines
|
||||
}
|
||||
|
||||
wrappedLines := make([]string, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
n := 0
|
||||
offset := 0
|
||||
lastWhitespaceIndex := -1
|
||||
for i, currChr := range line {
|
||||
rw := runewidth.RuneWidth(currChr)
|
||||
@ -79,28 +84,38 @@ func getMessageHeight(wrap bool, message string, width int) int {
|
||||
|
||||
if n > width {
|
||||
if currChr == ' ' {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i + 1
|
||||
n = 0
|
||||
} else if currChr == '-' {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
} else if lastWhitespaceIndex != -1 && lastWhitespaceIndex+1 != i {
|
||||
if line[lastWhitespaceIndex] == '-' {
|
||||
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex+1])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - lastWhitespaceIndex
|
||||
} else {
|
||||
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - lastWhitespaceIndex + 1
|
||||
}
|
||||
} else {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
}
|
||||
lineCount++
|
||||
lastWhitespaceIndex = -1
|
||||
} else if currChr == ' ' || currChr == '-' {
|
||||
lastWhitespaceIndex = i
|
||||
}
|
||||
}
|
||||
lineCount++
|
||||
|
||||
wrappedLines = append(wrappedLines, line[offset:])
|
||||
}
|
||||
|
||||
return lineCount
|
||||
return wrappedLines
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelDimensions(wrap bool, prompt string) (int, int, int, int) {
|
||||
@ -358,7 +373,9 @@ func (self *ConfirmationHelper) resizeMenu() {
|
||||
itemCount := self.c.Contexts().Menu.UnfilteredLen()
|
||||
offset := 3
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset)
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
promptLinesCount := self.layoutMenuPrompt(contentWidth)
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset+promptLinesCount)
|
||||
menuBottom := y1 - offset
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0)
|
||||
|
||||
@ -368,11 +385,39 @@ func (self *ConfirmationHelper) resizeMenu() {
|
||||
if selectedItem != nil {
|
||||
tooltip = self.TooltipForMenuItem(selectedItem)
|
||||
}
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
tooltipHeight := getMessageHeight(true, tooltip, contentWidth) + 2 // plus 2 for the frame
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
|
||||
}
|
||||
|
||||
// Wraps the lines of the menu prompt to the available width and rerenders the
|
||||
// menu if neeeded. Returns the number of lines the prompt takes up.
|
||||
func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int {
|
||||
oldPromptLines := self.c.Contexts().Menu.GetPromptLines()
|
||||
var promptLines []string
|
||||
prompt := self.c.Contexts().Menu.GetPrompt()
|
||||
if len(prompt) > 0 {
|
||||
promptLines = wrapMessageToWidth(true, prompt, contentWidth)
|
||||
promptLines = append(promptLines, "")
|
||||
}
|
||||
self.c.Contexts().Menu.SetPromptLines(promptLines)
|
||||
if len(oldPromptLines) != len(promptLines) {
|
||||
// The number of lines in the prompt has changed; this happens either
|
||||
// because we're now showing a menu that has a prompt, and the previous
|
||||
// menu didn't (or vice versa), or because the user is resizing the
|
||||
// terminal window while a menu with a prompt is open.
|
||||
|
||||
// We need to rerender to give the menu context a chance to update its
|
||||
// non-model items, and reinitialize the data it uses for converting
|
||||
// between view index and model index.
|
||||
_ = self.c.Contexts().Menu.HandleRender()
|
||||
|
||||
// Then we need to refocus to ensure the cursor is in the right place in
|
||||
// the view.
|
||||
_ = self.c.Contexts().Menu.HandleFocus(types.OnFocusOpts{})
|
||||
}
|
||||
return len(promptLines)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) resizeConfirmationPanel() {
|
||||
suggestionsViewHeight := 0
|
||||
if self.c.Views().Suggestions.Visible {
|
||||
|
@ -130,6 +130,7 @@ func (self *RefsHelper) CheckoutRemoteBranch(fullBranchName string, localBranchN
|
||||
Title: utils.ResolvePlaceholderString(self.c.Tr.RemoteBranchCheckoutTitle, map[string]string{
|
||||
"branchName": fullBranchName,
|
||||
}),
|
||||
Prompt: self.c.Tr.RemoteBranchCheckoutPrompt,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.CheckoutTypeNewBranch,
|
||||
|
@ -42,6 +42,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
|
||||
}
|
||||
|
||||
gui.State.Contexts.Menu.SetMenuItems(opts.Items, opts.ColumnAlignment)
|
||||
gui.State.Contexts.Menu.SetPrompt(opts.Prompt)
|
||||
gui.State.Contexts.Menu.SetSelection(0)
|
||||
|
||||
gui.Views.Menu.Title = opts.Title
|
||||
|
@ -159,6 +159,7 @@ const (
|
||||
|
||||
type CreateMenuOptions struct {
|
||||
Title string
|
||||
Prompt string // a message that will be displayed above the menu options
|
||||
Items []*MenuItem
|
||||
HideCancel bool
|
||||
ColumnAlignment []utils.Alignment
|
||||
|
@ -113,6 +113,7 @@ type TranslationSet struct {
|
||||
CheckoutByName string
|
||||
CheckoutByNameTooltip string
|
||||
RemoteBranchCheckoutTitle string
|
||||
RemoteBranchCheckoutPrompt string
|
||||
CheckoutTypeNewBranch string
|
||||
CheckoutTypeNewBranchTooltip string
|
||||
CheckoutTypeDetachedHead string
|
||||
@ -1079,6 +1080,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
CheckoutByName: "Checkout by name",
|
||||
CheckoutByNameTooltip: "Checkout by name. In the input box you can enter '-' to switch to the last branch.",
|
||||
RemoteBranchCheckoutTitle: "Checkout {{.branchName}}",
|
||||
RemoteBranchCheckoutPrompt: "How would you like to check out this branch?",
|
||||
CheckoutTypeNewBranch: "New local branch",
|
||||
CheckoutTypeNewBranchTooltip: "Checkout the remote branch as a local branch, tracking the remote branch.",
|
||||
CheckoutTypeDetachedHead: "Detached head",
|
||||
|
Reference in New Issue
Block a user