mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 20:36:03 +02:00
Fix pasting multi-line text into commit message panel (#4234)
- **PR Description** When pasting a multi-line commit message into the subject field of the commit editor, we would interpret the first newline as the confirmation for closing the editor, and then all remaining characters as whatever command they are bound to, resulting in executing all sorts of arbitrary commands. Now we recognize this being a paste, and interpret the first newline as moving to the description. Also, prevent tabs in the pasted content from switching to the respective other panel; simply insert four spaces instead, which should be good enough for the leading indentation in pasted code snippets, for example. Finally, disable pasting text into non-editable views; my assumption is that this is always a mistake, as it would execute arbitrary commands depending on what's in the clipboard. This depends on the terminal emulator supporting bracketed paste; I didn't find one on Mac that doesn't (I tested with Terminal.app, iTerm2, Ghostty, kitty, Alacritty, WezTerm, and VSCode's builtin terminal. It works well in all of them). I couldn't get it to work in Windows Terminal though, and I don't understand why, as it does seem to support bracketed paste (it works in bash). Fixes #3151 Fixes #4066 Fixes #4216
This commit is contained in:
commit
3915cb6e71
7 changed files with 105 additions and 6 deletions
2
go.mod
2
go.mod
|
@ -16,7 +16,7 @@ require (
|
|||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250207131741-38a8ffbf24fe
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250210123912-aba68ae65951
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
|
|
4
go.sum
4
go.sum
|
@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
|||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250207131741-38a8ffbf24fe h1:lNTwIp53mU5pfKYFinIsbUsd6mNxMit4IXcJUnn1Pc0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250207131741-38a8ffbf24fe/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250210123912-aba68ae65951 h1:7/3M0yosAM9/aLAjTfzSJWhsWjT860ZVe4T76RPwE2k=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250210123912-aba68ae65951/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a h1:UDeJ3EBk04bXDLOPvuqM3on8HvyJfISw0+UMqW+0a4g=
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a/go.mod h1:FSWDLKT0NQpntbDd1H3lbz51fhCVlMzy/J0S6nM727Q=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
|
|
|
@ -28,7 +28,7 @@ func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOp
|
|||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: self.switchToCommitMessage,
|
||||
Handler: self.handleTogglePanel,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
|
@ -75,6 +75,32 @@ func (self *CommitDescriptionController) switchToCommitMessage() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) handleTogglePanel() error {
|
||||
// The default keybinding for this action is "<tab>", which means that we
|
||||
// also get here when pasting multi-line text that contains tabs. In that
|
||||
// case we don't want to toggle the panel, but insert the tab as a character
|
||||
// (somehow, see below).
|
||||
//
|
||||
// Only do this if the TogglePanel command is actually mapped to "<tab>"
|
||||
// (the default). If it's not, we can only hope that it's mapped to some
|
||||
// ctrl key or fn key, which is unlikely to occur in pasted text. And if
|
||||
// they mapped some *other* command to "<tab>", then we're totally out of
|
||||
// luck.
|
||||
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "<tab>" {
|
||||
// Handling tabs in pasted commit messages is not optimal, but hopefully
|
||||
// good enough for now. We simply insert 4 spaces without worrying about
|
||||
// column alignment. This works well enough for leading indentation,
|
||||
// which is common in pasted code snippets.
|
||||
view := self.Context().GetView()
|
||||
for range 4 {
|
||||
view.Editor.Edit(view, gocui.KeySpace, ' ', 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.switchToCommitMessage()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) close() error {
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
return nil
|
||||
|
|
|
@ -48,7 +48,7 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
|
|||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: self.switchToCommitDescription,
|
||||
Handler: self.handleTogglePanel,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.CommitMessage.CommitMenu),
|
||||
|
@ -105,6 +105,32 @@ func (self *CommitMessageController) switchToCommitDescription() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handleTogglePanel() error {
|
||||
// The default keybinding for this action is "<tab>", which means that we
|
||||
// also get here when pasting multi-line text that contains tabs. In that
|
||||
// case we don't want to toggle the panel, but insert the tab as a character
|
||||
// (somehow, see below).
|
||||
//
|
||||
// Only do this if the TogglePanel command is actually mapped to "<tab>"
|
||||
// (the default). If it's not, we can only hope that it's mapped to some
|
||||
// ctrl key or fn key, which is unlikely to occur in pasted text. And if
|
||||
// they mapped some *other* command to "<tab>", then we're totally out of
|
||||
// luck.
|
||||
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "<tab>" {
|
||||
// It is unlikely that a pasted commit message contains a tab in the
|
||||
// subject line, so it shouldn't matter too much how we handle it.
|
||||
// Simply insert 4 spaces instead; all that matters is that we don't
|
||||
// switch to the description panel.
|
||||
view := self.context().GetView()
|
||||
for range 4 {
|
||||
view.Editor.Edit(view, gocui.KeySpace, ' ', 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.switchToCommitDescription()
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
currentIndex := self.context().GetSelectedIndex()
|
||||
newIndex := currentIndex + value
|
||||
|
@ -140,6 +166,20 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e
|
|||
}
|
||||
|
||||
func (self *CommitMessageController) confirm() error {
|
||||
// The default keybinding for this action is "<enter>", which means that we
|
||||
// also get here when pasting multi-line text that contains newlines. In
|
||||
// that case we don't want to confirm the commit, but switch to the
|
||||
// description panel instead so that the rest of the pasted text goes there.
|
||||
//
|
||||
// Only do this if the SubmitEditorText command is actually mapped to
|
||||
// "<enter>" (the default). If it's not, we can only hope that it's mapped
|
||||
// to some ctrl key or fn key, which is unlikely to occur in pasted text.
|
||||
// And if they mapped some *other* command to "<enter>", then we're totally
|
||||
// out of luck.
|
||||
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.SubmitEditorText == "<enter>" {
|
||||
return self.switchToCommitDescription()
|
||||
}
|
||||
|
||||
return self.c.Helpers().Commits.HandleCommitConfirm()
|
||||
}
|
||||
|
||||
|
|
24
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
24
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
|
@ -157,6 +157,8 @@ type Gui struct {
|
|||
// If Mouse is true then mouse events will be enabled.
|
||||
Mouse bool
|
||||
|
||||
IsPasting bool
|
||||
|
||||
// If InputEsc is true, when ESC sequence is in the buffer and it doesn't
|
||||
// match any known sequence, ESC means KeyEsc.
|
||||
InputEsc bool
|
||||
|
@ -759,6 +761,7 @@ func (g *Gui) MainLoop() error {
|
|||
}()
|
||||
|
||||
Screen.EnableFocus()
|
||||
Screen.EnablePaste()
|
||||
|
||||
previousEnableMouse := false
|
||||
for {
|
||||
|
@ -847,6 +850,9 @@ func (g *Gui) handleEvent(ev *GocuiEvent) error {
|
|||
return nil
|
||||
case eventFocus:
|
||||
return g.onFocus(ev)
|
||||
case eventPaste:
|
||||
g.IsPasting = ev.Start
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
@ -1305,6 +1311,20 @@ func (g *Gui) onKey(ev *GocuiEvent) error {
|
|||
switch ev.Type {
|
||||
case eventKey:
|
||||
|
||||
// When pasting text in Ghostty, it sends us '\r' instead of '\n' for
|
||||
// newlines. I actually don't quite understand why, because from reading
|
||||
// Ghostty's source code (e.g.
|
||||
// https://github.com/ghostty-org/ghostty/commit/010338354a0) it does
|
||||
// this conversion only for non-bracketed paste mode, but I'm seeing it
|
||||
// in bracketed paste mode. Whatever I'm missing here, converting '\r'
|
||||
// back to '\n' fixes pasting multi-line text from Ghostty, and doesn't
|
||||
// seem harmful for other terminal emulators.
|
||||
//
|
||||
// KeyCtrlJ (int value 10) is '\r', and KeyCtrlM (int value 13) is '\n'.
|
||||
if g.IsPasting && ev.Key == KeyCtrlJ {
|
||||
ev.Key = KeyCtrlM
|
||||
}
|
||||
|
||||
err := g.execKeybindings(g.currentView, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1469,6 +1489,10 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
|
|||
var globalKb *keybinding
|
||||
var matchingParentViewKb *keybinding
|
||||
|
||||
if g.IsPasting && v != nil && !v.Editable {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if we're searching, and we've hit n/N/Esc, we ignore the default keybinding
|
||||
if v != nil && v.IsSearching() && ev.Mod == ModNone {
|
||||
if eventMatchesKey(ev, g.NextSearchMatchKey) {
|
||||
|
|
9
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
9
vendor/github.com/jesseduffield/gocui/tcell_driver.go
generated
vendored
|
@ -155,6 +155,8 @@ type gocuiEventType uint8
|
|||
// The 'MouseX' and 'MouseY' fields are valid if 'Type' is 'eventMouse'.
|
||||
// The 'Width' and 'Height' fields are valid if 'Type' is 'eventResize'.
|
||||
// The 'Focused' field is valid if 'Type' is 'eventFocus'.
|
||||
// The 'Start' field is valid if 'Type' is 'eventPaste'. It is true for the
|
||||
// beginning of a paste operation, false for the end.
|
||||
// The 'Err' field is valid if 'Type' is 'eventError'.
|
||||
type GocuiEvent struct {
|
||||
Type gocuiEventType
|
||||
|
@ -167,6 +169,7 @@ type GocuiEvent struct {
|
|||
MouseX int
|
||||
MouseY int
|
||||
Focused bool
|
||||
Start bool
|
||||
N int
|
||||
}
|
||||
|
||||
|
@ -178,6 +181,7 @@ const (
|
|||
eventMouse
|
||||
eventMouseMove // only used when no button is down, otherwise it's eventMouse
|
||||
eventFocus
|
||||
eventPaste
|
||||
eventInterrupt
|
||||
eventError
|
||||
eventRaw
|
||||
|
@ -417,6 +421,11 @@ func (g *Gui) pollEvent() GocuiEvent {
|
|||
Type: eventFocus,
|
||||
Focused: tev.Focused,
|
||||
}
|
||||
case *tcell.EventPaste:
|
||||
return GocuiEvent{
|
||||
Type: eventPaste,
|
||||
Start: tev.Start(),
|
||||
}
|
||||
default:
|
||||
return GocuiEvent{Type: eventNone}
|
||||
}
|
||||
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
|
@ -171,7 +171,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
|
|||
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20250207131741-38a8ffbf24fe
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20250210123912-aba68ae65951
|
||||
## explicit; go 1.12
|
||||
github.com/jesseduffield/gocui
|
||||
# github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue