diff --git a/go.mod b/go.mod index a8db15946..b2da578e3 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 6d2364b82..d1259e996 100644 --- a/go.sum +++ b/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= diff --git a/pkg/gui/controllers/commit_description_controller.go b/pkg/gui/controllers/commit_description_controller.go index aea6cfbdf..4337a1e4b 100644 --- a/pkg/gui/controllers/commit_description_controller.go +++ b/pkg/gui/controllers/commit_description_controller.go @@ -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 "", 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 "" + // (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 "", then we're totally out of + // luck. + if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "" { + // 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 diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go index 28168ef18..6f0773801 100644 --- a/pkg/gui/controllers/commit_message_controller.go +++ b/pkg/gui/controllers/commit_message_controller.go @@ -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 "", 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 "" + // (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 "", then we're totally out of + // luck. + if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "" { + // 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 "", 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 + // "" (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 "", then we're totally + // out of luck. + if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.SubmitEditorText == "" { + return self.switchToCommitDescription() + } + return self.c.Helpers().Commits.HandleCommitConfirm() } diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index 87cc28321..03d55912a 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -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) { diff --git a/vendor/github.com/jesseduffield/gocui/tcell_driver.go b/vendor/github.com/jesseduffield/gocui/tcell_driver.go index 96e816b2f..4199f7abb 100644 --- a/vendor/github.com/jesseduffield/gocui/tcell_driver.go +++ b/vendor/github.com/jesseduffield/gocui/tcell_driver.go @@ -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} } diff --git a/vendor/modules.txt b/vendor/modules.txt index 6a6d839a6..0f7e17462 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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