Let a menu's filter receive pasted text in one piece

The filter takes a single line of text. Drop the newlines at the start
and end of the pasted text, and replace the remaining newlines and tabs
with spaces. A pasted newline therefore no longer runs the selected
menu item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-11-12 10:22:50 +01:00
co-authored by Claude Opus 5.5
parent f6ceabdebe
commit e68f195e33
6 changed files with 94 additions and 3 deletions
+30
View File
@@ -1,6 +1,8 @@
package gui
import (
"strings"
"github.com/jesseduffield/lazygit/pkg/gocui"
)
@@ -76,6 +78,34 @@ func (gui *Gui) menuFilterEditor(v *gocui.View, key gocui.Key) bool {
return true
}
func (gui *Gui) pasteIntoMenuFilter(text string) {
v := gui.Views.MenuFilter
if typePastedTextIntoSingleLineView(v, text) {
gui.onMenuFilterContentChanged(v.TextArea.GetContent())
}
}
// typePastedTextIntoSingleLineView returns false if nothing is left of the text
// to type.
func typePastedTextIntoSingleLineView(v *gocui.View, text string) bool {
text = pastedTextAsSingleLine(text)
if text == "" {
return false
}
v.TextArea.TypeString(text)
v.RenderTextArea()
return true
}
// Views that take a single line of text get pasted text without the newlines at
// its start and end, which are often part of a copied line, and with spaces in
// place of the newlines and tabs inside it.
func pastedTextAsSingleLine(text string) string {
text = strings.Trim(text, "\n")
return strings.NewReplacer("\n", " ", "\t", " ").Replace(text)
}
func (gui *Gui) onMenuFilterContentChanged(content string) {
menuContext := gui.State.Contexts.Menu
menuContext.SetFilterStarted(true)
+27
View File
@@ -0,0 +1,27 @@
package gui
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPastedTextAsSingleLine(t *testing.T) {
scenarios := []struct {
name string
text string
expected string
}{
{"single line", "foo bar", "foo bar"},
{"newlines at start and end", "\n\nfoo\n", "foo"},
{"newlines inside", "foo\nbar\n\nbaz", "foo bar baz"},
{"tabs", "\tfoo\tbar", " foo bar"},
{"only newlines", "\n\n", ""},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
assert.Equal(t, s.expected, pastedTextAsSingleLine(s.text))
})
}
}
+4
View File
@@ -150,6 +150,10 @@ func (gui *Gui) createAllViews() error {
gui.Views.MenuFilter.Frame = false
gui.Views.MenuFilter.Editable = true
gui.Views.MenuFilter.Editor = gocui.EditorFunc(gui.menuFilterEditor)
gui.Views.MenuFilter.OnPaste = func(text string) error {
gui.pasteIntoMenuFilter(text)
return nil
}
// The filter row belongs to the menu: it shares the menu's focus, and keys
// that the input field doesn't take are the menu's to handle.
gui.Views.MenuFilterFrame.ParentView = gui.Views.Menu
@@ -27,10 +27,7 @@ var PasteIntoKeybindingsMenu = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
Lines(
/* EXPECTED:
Contains(`??`).Contains(`myfile`),
ACTUAL: */
Contains(`A `).Contains(`myfile`),
)
},
})
@@ -0,0 +1,32 @@
package filter_and_search
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var PasteIntoMenuFilter = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Paste text into the filter of a menu that filters as you type",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.CreateFile("myfile", "myfile")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Press(keys.Universal.OptionMenu)
t.ExpectPopup().Menu().
Title(Equals("Keybindings")).
Paste("\nIgnore\n").
Lines(
Contains(`─── Local`),
Contains(`Ignore`).IsSelected(),
)
t.Views().MenuFilter().
Content(Equals("Ignore"))
},
})
+1
View File
@@ -338,6 +338,7 @@ var tests = []*components.IntegrationTest{
filter_and_search.NestedFilterTransient,
filter_and_search.NewSearch,
filter_and_search.PasteIntoKeybindingsMenu,
filter_and_search.PasteIntoMenuFilter,
filter_and_search.RerenderTheSearchedMainView,
filter_and_search.SearchALongDiff,
filter_and_search.SearchStatusAfterARerender,