From 99ad6005e82e49e84121121feb4255e09dbb8673 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 9 Mar 2024 09:53:25 +0100 Subject: [PATCH 1/6] Bump gocui --- go.mod | 8 +- go.sum | 14 +- .../gdamore/tcell/v2/console_win.go | 6 +- .../jesseduffield/gocui/text_area.go | 168 ++++++++++++++++-- vendor/golang.org/x/sys/unix/aliases.go | 2 +- .../x/sys/unix/syscall_darwin_libSystem.go | 2 +- .../golang.org/x/sys/unix/syscall_freebsd.go | 12 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 99 +++++++++++ .../golang.org/x/sys/unix/zsyscall_linux.go | 10 ++ vendor/golang.org/x/sys/unix/ztypes_linux.go | 60 +++++++ vendor/modules.txt | 8 +- 11 files changed, 351 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index bc039bc57..58ad19c3c 100644 --- a/go.mod +++ b/go.mod @@ -9,14 +9,14 @@ require ( github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 github.com/creack/pty v1.1.11 github.com/fsmiamoto/git-todo-parser v0.0.5 - github.com/gdamore/tcell/v2 v2.7.3 + github.com/gdamore/tcell/v2 v2.7.4 github.com/go-errors/errors v1.5.1 github.com/gookit/color v1.4.2 github.com/imdario/mergo v0.3.11 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.20240303173746-f2b0f1f68dd8 + github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e @@ -74,8 +74,8 @@ require ( github.com/xanzy/ssh-agent v0.2.1 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect golang.org/x/net v0.7.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 84e171721..0e728ff52 100644 --- a/go.sum +++ b/go.sum @@ -89,8 +89,8 @@ github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLI github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= -github.com/gdamore/tcell/v2 v2.7.3 h1:YLQlOj5F0hSlKy5TJvlych29+WTcJzbElnLYwx8gvdg= -github.com/gdamore/tcell/v2 v2.7.3/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= +github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU= +github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= @@ -187,8 +187,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.20240303173746-f2b0f1f68dd8 h1:DGyAjpaAnxDuKO4MEoFjifhkUV7sU6znMR9eRfjjvn0= -github.com/jesseduffield/gocui v0.3.1-0.20240303173746-f2b0f1f68dd8/go.mod h1:lLLfxEGyIvvkzzpHdKkfgIVFmxqEejeACxKMVxSHLeM= +github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de h1:2ww1SWgakihE8hFxZ7L3agVeGpA6qwW5vdnhFUXKMQo= +github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0= github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo= github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY= @@ -470,13 +470,15 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/vendor/github.com/gdamore/tcell/v2/console_win.go b/vendor/github.com/gdamore/tcell/v2/console_win.go index 92ae4e5c2..e2652509e 100644 --- a/vendor/github.com/gdamore/tcell/v2/console_win.go +++ b/vendor/github.com/gdamore/tcell/v2/console_win.go @@ -341,12 +341,12 @@ func (s *cScreen) disengage() { } } else if !s.disableAlt { s.clearScreen(StyleDefault, s.vten) + s.setCursorPos(0, 0, false) } + s.setCursorInfo(&s.ocursor) + s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y)) s.setInMode(s.oimode) s.setOutMode(s.oomode) - s.setBufferSize(int(s.oscreen.size.x), int(s.oscreen.size.y)) - s.setCursorPos(0, 0, false) - s.setCursorInfo(&s.ocursor) _, _, _ = procSetConsoleTextAttribute.Call( uintptr(s.out), uintptr(s.mapStyle(StyleDefault))) diff --git a/vendor/github.com/jesseduffield/gocui/text_area.go b/vendor/github.com/jesseduffield/gocui/text_area.go index ca4809107..ebd6a6bfa 100644 --- a/vendor/github.com/jesseduffield/gocui/text_area.go +++ b/vendor/github.com/jesseduffield/gocui/text_area.go @@ -11,11 +11,59 @@ const ( WORD_SEPARATORS = "*?_+-.[]~=/&;!#$%^(){}<>" ) +type CursorMapping struct { + Orig int + Wrapped int +} + type TextArea struct { - content []rune - cursor int - overwrite bool - clipboard string + content []rune + wrappedContent []rune + cursorMapping []CursorMapping + cursor int + overwrite bool + clipboard string + AutoWrap bool + AutoWrapWidth int +} + +func AutoWrapContent(content []rune, autoWrapWidth int) ([]rune, []CursorMapping) { + estimatedNumberOfSoftLineBreaks := len(content) / autoWrapWidth + cursorMapping := make([]CursorMapping, 0, estimatedNumberOfSoftLineBreaks) + wrappedContent := make([]rune, 0, len(content)+estimatedNumberOfSoftLineBreaks) + startOfLine := 0 + indexOfLastWhitespace := -1 + + for currentPos, r := range content { + if r == '\n' { + wrappedContent = append(wrappedContent, content[startOfLine:currentPos+1]...) + startOfLine = currentPos + 1 + indexOfLastWhitespace = -1 + } else { + if r == ' ' { + indexOfLastWhitespace = currentPos + 1 + } else if currentPos-startOfLine >= autoWrapWidth && indexOfLastWhitespace >= 0 { + wrapAt := indexOfLastWhitespace + wrappedContent = append(wrappedContent, content[startOfLine:wrapAt]...) + wrappedContent = append(wrappedContent, '\n') + cursorMapping = append(cursorMapping, CursorMapping{wrapAt, len(wrappedContent)}) + startOfLine = wrapAt + indexOfLastWhitespace = -1 + } + } + } + + wrappedContent = append(wrappedContent, content[startOfLine:]...) + + return wrappedContent, cursorMapping +} + +func (self *TextArea) autoWrapContent() { + if self.AutoWrap { + self.wrappedContent, self.cursorMapping = AutoWrapContent(self.content, self.AutoWrapWidth) + } else { + self.wrappedContent, self.cursorMapping = self.content, []CursorMapping{} + } } func (self *TextArea) TypeRune(r rune) { @@ -27,6 +75,7 @@ func (self *TextArea) TypeRune(r rune) { append([]rune{r}, self.content[self.cursor:]...)..., ) } + self.autoWrapContent() self.cursor++ } @@ -37,6 +86,7 @@ func (self *TextArea) BackSpaceChar() { } self.content = append(self.content[:self.cursor-1], self.content[self.cursor:]...) + self.autoWrapContent() self.cursor-- } @@ -46,6 +96,7 @@ func (self *TextArea) DeleteChar() { } self.content = append(self.content[:self.cursor], self.content[self.cursor+1:]...) + self.autoWrapContent() } func (self *TextArea) MoveCursorLeft() { @@ -123,6 +174,10 @@ func (self *TextArea) MoveCursorDown() { } func (self *TextArea) GetContent() string { + return string(self.wrappedContent) +} + +func (self *TextArea) GetUnwrappedContent() string { return string(self.content) } @@ -144,14 +199,24 @@ func (self *TextArea) DeleteToStartOfLine() { self.content = append(self.content[:self.cursor-1], self.content[self.cursor:]...) self.cursor-- + self.autoWrapContent() return } + // otherwise, if we're at a soft line start, skip left past the soft line + // break, so we'll end up deleting the previous line. This seems like the + // only reasonable behavior in this case, as you can't delete just the soft + // line break. + if self.atSoftLineStart() { + self.cursor-- + } + // otherwise, you delete everything up to the start of the current line, without // deleting the newline character newlineIndex := self.closestNewlineOnLeft() self.clipboard = string(self.content[newlineIndex+1 : self.cursor]) self.content = append(self.content[:newlineIndex+1], self.content[self.cursor:]...) + self.autoWrapContent() self.cursor = newlineIndex + 1 } @@ -159,18 +224,30 @@ func (self *TextArea) DeleteToEndOfLine() { if self.atEnd() { return } + + // if we're at the end of the line, delete just the newline character if self.atLineEnd() { self.content = append(self.content[:self.cursor], self.content[self.cursor+1:]...) + self.autoWrapContent() return } + // otherwise, if we're at a soft line end, skip right past the soft line + // break, so we'll end up deleting the next line. This seems like the + // only reasonable behavior in this case, as you can't delete just the soft + // line break. + if self.atSoftLineEnd() { + self.cursor++ + } + lineEndIndex := self.closestNewlineOnRight() self.clipboard = string(self.content[self.cursor:lineEndIndex]) self.content = append(self.content[:self.cursor], self.content[lineEndIndex:]...) + self.autoWrapContent() } func (self *TextArea) GoToStartOfLine() { - if self.atLineStart() { + if self.atSoftLineStart() { return } @@ -181,15 +258,21 @@ func (self *TextArea) GoToStartOfLine() { } func (self *TextArea) closestNewlineOnLeft() int { + wrappedCursor := self.origCursorToWrappedCursor(self.cursor) + newlineIndex := -1 - for i, r := range self.content[0:self.cursor] { + for i, r := range self.wrappedContent[0:wrappedCursor] { if r == '\n' { newlineIndex = i } } - return newlineIndex + unwrappedNewlineIndex := self.wrappedCursorToOrigCursor(newlineIndex) + if unwrappedNewlineIndex >= 0 && self.content[unwrappedNewlineIndex] != '\n' { + unwrappedNewlineIndex-- + } + return unwrappedNewlineIndex } func (self *TextArea) GoToEndOfLine() { @@ -198,12 +281,22 @@ func (self *TextArea) GoToEndOfLine() { } self.cursor = self.closestNewlineOnRight() + + // If the end of line is a soft line break, we need to move left by one so + // that we end up at the last whitespace before the line break. Otherwise + // we'd be at the start of the next line, since the newline character + // doesn't really exist in the real content. + if self.cursor < len(self.content) && self.content[self.cursor] != '\n' { + self.cursor-- + } } func (self *TextArea) closestNewlineOnRight() int { - for i, r := range self.content[self.cursor:] { + wrappedCursor := self.origCursorToWrappedCursor(self.cursor) + + for i, r := range self.wrappedContent[wrappedCursor:] { if r == '\n' { - return self.cursor + i + return self.wrappedCursorToOrigCursor(wrappedCursor + i) } } @@ -215,11 +308,23 @@ func (self *TextArea) atLineStart() bool { (len(self.content) > self.cursor-1 && self.content[self.cursor-1] == '\n') } +func (self *TextArea) atSoftLineStart() bool { + wrappedCursor := self.origCursorToWrappedCursor(self.cursor) + return wrappedCursor == 0 || + (len(self.wrappedContent) > wrappedCursor-1 && self.wrappedContent[wrappedCursor-1] == '\n') +} + func (self *TextArea) atLineEnd() bool { return self.atEnd() || (len(self.content) > self.cursor && self.content[self.cursor] == '\n') } +func (self *TextArea) atSoftLineEnd() bool { + wrappedCursor := self.origCursorToWrappedCursor(self.cursor) + return wrappedCursor == len(self.wrappedContent) || + (len(self.wrappedContent) > wrappedCursor+1 && self.wrappedContent[wrappedCursor+1] == '\n') +} + func (self *TextArea) BackSpaceWord() { if self.cursor == 0 { return @@ -246,16 +351,50 @@ func (self *TextArea) BackSpaceWord() { self.clipboard = string(self.content[self.cursor:right]) self.content = append(self.content[:self.cursor], self.content[right:]...) + self.autoWrapContent() } func (self *TextArea) Yank() { self.TypeString(self.clipboard) } +func origCursorToWrappedCursor(origCursor int, cursorMapping []CursorMapping) int { + prevMapping := CursorMapping{0, 0} + for _, mapping := range cursorMapping { + if origCursor < mapping.Orig { + break + } + prevMapping = mapping + } + + return origCursor + prevMapping.Wrapped - prevMapping.Orig +} + +func (self *TextArea) origCursorToWrappedCursor(origCursor int) int { + return origCursorToWrappedCursor(origCursor, self.cursorMapping) +} + +func wrappedCursorToOrigCursor(wrappedCursor int, cursorMapping []CursorMapping) int { + prevMapping := CursorMapping{0, 0} + for _, mapping := range cursorMapping { + if wrappedCursor < mapping.Wrapped { + break + } + prevMapping = mapping + } + + return wrappedCursor + prevMapping.Orig - prevMapping.Wrapped +} + +func (self *TextArea) wrappedCursorToOrigCursor(wrappedCursor int) int { + return wrappedCursorToOrigCursor(wrappedCursor, self.cursorMapping) +} + func (self *TextArea) GetCursorXY() (int, int) { cursorX := 0 cursorY := 0 - for _, r := range self.content[0:self.cursor] { + wrappedCursor := self.origCursorToWrappedCursor(self.cursor) + for _, r := range self.wrappedContent[0:wrappedCursor] { if r == '\n' { cursorY++ cursorX = 0 @@ -278,15 +417,15 @@ func (self *TextArea) SetCursor2D(x int, y int) { } newCursor := 0 - for _, r := range self.content { + for _, r := range self.wrappedContent { if x <= 0 && y == 0 { - self.cursor = newCursor + self.cursor = self.wrappedCursorToOrigCursor(newCursor) return } if r == '\n' { if y == 0 { - self.cursor = newCursor + self.cursor = self.wrappedCursorToOrigCursor(newCursor) return } y-- @@ -304,11 +443,12 @@ func (self *TextArea) SetCursor2D(x int, y int) { return } - self.cursor = newCursor + self.cursor = self.wrappedCursorToOrigCursor(newCursor) } func (self *TextArea) Clear() { self.content = []rune{} + self.wrappedContent = []rune{} self.cursor = 0 } diff --git a/vendor/golang.org/x/sys/unix/aliases.go b/vendor/golang.org/x/sys/unix/aliases.go index e7d3df4bd..b0e419857 100644 --- a/vendor/golang.org/x/sys/unix/aliases.go +++ b/vendor/golang.org/x/sys/unix/aliases.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) && go1.9 +//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos package unix diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go b/vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go index 16dc69937..2f0fa76e4 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build darwin && go1.12 +//go:build darwin package unix diff --git a/vendor/golang.org/x/sys/unix/syscall_freebsd.go b/vendor/golang.org/x/sys/unix/syscall_freebsd.go index 64d1bb4db..2b57e0f73 100644 --- a/vendor/golang.org/x/sys/unix/syscall_freebsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_freebsd.go @@ -13,6 +13,7 @@ package unix import ( + "errors" "sync" "unsafe" ) @@ -169,25 +170,26 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) { func Uname(uname *Utsname) error { mib := []_C_int{CTL_KERN, KERN_OSTYPE} n := unsafe.Sizeof(uname.Sysname) - if err := sysctl(mib, &uname.Sysname[0], &n, nil, 0); err != nil { + // Suppress ENOMEM errors to be compatible with the C library __xuname() implementation. + if err := sysctl(mib, &uname.Sysname[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) { return err } mib = []_C_int{CTL_KERN, KERN_HOSTNAME} n = unsafe.Sizeof(uname.Nodename) - if err := sysctl(mib, &uname.Nodename[0], &n, nil, 0); err != nil { + if err := sysctl(mib, &uname.Nodename[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) { return err } mib = []_C_int{CTL_KERN, KERN_OSRELEASE} n = unsafe.Sizeof(uname.Release) - if err := sysctl(mib, &uname.Release[0], &n, nil, 0); err != nil { + if err := sysctl(mib, &uname.Release[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) { return err } mib = []_C_int{CTL_KERN, KERN_VERSION} n = unsafe.Sizeof(uname.Version) - if err := sysctl(mib, &uname.Version[0], &n, nil, 0); err != nil { + if err := sysctl(mib, &uname.Version[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) { return err } @@ -205,7 +207,7 @@ func Uname(uname *Utsname) error { mib = []_C_int{CTL_HW, HW_MACHINE} n = unsafe.Sizeof(uname.Machine) - if err := sysctl(mib, &uname.Machine[0], &n, nil, 0); err != nil { + if err := sysctl(mib, &uname.Machine[0], &n, nil, 0); err != nil && !errors.Is(err, ENOMEM) { return err } diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index 0f85e29e6..5682e2628 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -1849,6 +1849,105 @@ func Dup2(oldfd, newfd int) error { //sys Fsmount(fd int, flags int, mountAttrs int) (fsfd int, err error) //sys Fsopen(fsName string, flags int) (fd int, err error) //sys Fspick(dirfd int, pathName string, flags int) (fd int, err error) + +//sys fsconfig(fd int, cmd uint, key *byte, value *byte, aux int) (err error) + +func fsconfigCommon(fd int, cmd uint, key string, value *byte, aux int) (err error) { + var keyp *byte + if keyp, err = BytePtrFromString(key); err != nil { + return + } + return fsconfig(fd, cmd, keyp, value, aux) +} + +// FsconfigSetFlag is equivalent to fsconfig(2) called +// with cmd == FSCONFIG_SET_FLAG. +// +// fd is the filesystem context to act upon. +// key the parameter key to set. +func FsconfigSetFlag(fd int, key string) (err error) { + return fsconfigCommon(fd, FSCONFIG_SET_FLAG, key, nil, 0) +} + +// FsconfigSetString is equivalent to fsconfig(2) called +// with cmd == FSCONFIG_SET_STRING. +// +// fd is the filesystem context to act upon. +// key the parameter key to set. +// value is the parameter value to set. +func FsconfigSetString(fd int, key string, value string) (err error) { + var valuep *byte + if valuep, err = BytePtrFromString(value); err != nil { + return + } + return fsconfigCommon(fd, FSCONFIG_SET_STRING, key, valuep, 0) +} + +// FsconfigSetBinary is equivalent to fsconfig(2) called +// with cmd == FSCONFIG_SET_BINARY. +// +// fd is the filesystem context to act upon. +// key the parameter key to set. +// value is the parameter value to set. +func FsconfigSetBinary(fd int, key string, value []byte) (err error) { + if len(value) == 0 { + return EINVAL + } + return fsconfigCommon(fd, FSCONFIG_SET_BINARY, key, &value[0], len(value)) +} + +// FsconfigSetPath is equivalent to fsconfig(2) called +// with cmd == FSCONFIG_SET_PATH. +// +// fd is the filesystem context to act upon. +// key the parameter key to set. +// path is a non-empty path for specified key. +// atfd is a file descriptor at which to start lookup from or AT_FDCWD. +func FsconfigSetPath(fd int, key string, path string, atfd int) (err error) { + var valuep *byte + if valuep, err = BytePtrFromString(path); err != nil { + return + } + return fsconfigCommon(fd, FSCONFIG_SET_PATH, key, valuep, atfd) +} + +// FsconfigSetPathEmpty is equivalent to fsconfig(2) called +// with cmd == FSCONFIG_SET_PATH_EMPTY. The same as +// FconfigSetPath but with AT_PATH_EMPTY implied. +func FsconfigSetPathEmpty(fd int, key string, path string, atfd int) (err error) { + var valuep *byte + if valuep, err = BytePtrFromString(path); err != nil { + return + } + return fsconfigCommon(fd, FSCONFIG_SET_PATH_EMPTY, key, valuep, atfd) +} + +// FsconfigSetFd is equivalent to fsconfig(2) called +// with cmd == FSCONFIG_SET_FD. +// +// fd is the filesystem context to act upon. +// key the parameter key to set. +// value is a file descriptor to be assigned to specified key. +func FsconfigSetFd(fd int, key string, value int) (err error) { + return fsconfigCommon(fd, FSCONFIG_SET_FD, key, nil, value) +} + +// FsconfigCreate is equivalent to fsconfig(2) called +// with cmd == FSCONFIG_CMD_CREATE. +// +// fd is the filesystem context to act upon. +func FsconfigCreate(fd int) (err error) { + return fsconfig(fd, FSCONFIG_CMD_CREATE, nil, nil, 0) +} + +// FsconfigReconfigure is equivalent to fsconfig(2) called +// with cmd == FSCONFIG_CMD_RECONFIGURE. +// +// fd is the filesystem context to act upon. +func FsconfigReconfigure(fd int) (err error) { + return fsconfig(fd, FSCONFIG_CMD_RECONFIGURE, nil, nil, 0) +} + //sys Getdents(fd int, buf []byte) (n int, err error) = SYS_GETDENTS64 //sysnb Getpgid(pid int) (pgid int, err error) diff --git a/vendor/golang.org/x/sys/unix/zsyscall_linux.go b/vendor/golang.org/x/sys/unix/zsyscall_linux.go index 1488d2712..87d8612a1 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_linux.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_linux.go @@ -906,6 +906,16 @@ func Fspick(dirfd int, pathName string, flags int) (fd int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func fsconfig(fd int, cmd uint, key *byte, value *byte, aux int) (err error) { + _, _, e1 := Syscall6(SYS_FSCONFIG, uintptr(fd), uintptr(cmd), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(value)), uintptr(aux), 0) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func Getdents(fd int, buf []byte) (n int, err error) { var _p0 unsafe.Pointer if len(buf) > 0 { diff --git a/vendor/golang.org/x/sys/unix/ztypes_linux.go b/vendor/golang.org/x/sys/unix/ztypes_linux.go index dc0c955ee..eff6bcdef 100644 --- a/vendor/golang.org/x/sys/unix/ztypes_linux.go +++ b/vendor/golang.org/x/sys/unix/ztypes_linux.go @@ -836,6 +836,15 @@ const ( FSPICK_EMPTY_PATH = 0x8 FSMOUNT_CLOEXEC = 0x1 + + FSCONFIG_SET_FLAG = 0x0 + FSCONFIG_SET_STRING = 0x1 + FSCONFIG_SET_BINARY = 0x2 + FSCONFIG_SET_PATH = 0x3 + FSCONFIG_SET_PATH_EMPTY = 0x4 + FSCONFIG_SET_FD = 0x5 + FSCONFIG_CMD_CREATE = 0x6 + FSCONFIG_CMD_RECONFIGURE = 0x7 ) type OpenHow struct { @@ -1550,6 +1559,7 @@ const ( IFLA_DEVLINK_PORT = 0x3e IFLA_GSO_IPV4_MAX_SIZE = 0x3f IFLA_GRO_IPV4_MAX_SIZE = 0x40 + IFLA_DPLL_PIN = 0x41 IFLA_PROTO_DOWN_REASON_UNSPEC = 0x0 IFLA_PROTO_DOWN_REASON_MASK = 0x1 IFLA_PROTO_DOWN_REASON_VALUE = 0x2 @@ -1565,6 +1575,7 @@ const ( IFLA_INET6_ICMP6STATS = 0x6 IFLA_INET6_TOKEN = 0x7 IFLA_INET6_ADDR_GEN_MODE = 0x8 + IFLA_INET6_RA_MTU = 0x9 IFLA_BR_UNSPEC = 0x0 IFLA_BR_FORWARD_DELAY = 0x1 IFLA_BR_HELLO_TIME = 0x2 @@ -1612,6 +1623,9 @@ const ( IFLA_BR_MCAST_MLD_VERSION = 0x2c IFLA_BR_VLAN_STATS_PER_PORT = 0x2d IFLA_BR_MULTI_BOOLOPT = 0x2e + IFLA_BR_MCAST_QUERIER_STATE = 0x2f + IFLA_BR_FDB_N_LEARNED = 0x30 + IFLA_BR_FDB_MAX_LEARNED = 0x31 IFLA_BRPORT_UNSPEC = 0x0 IFLA_BRPORT_STATE = 0x1 IFLA_BRPORT_PRIORITY = 0x2 @@ -1649,6 +1663,14 @@ const ( IFLA_BRPORT_BACKUP_PORT = 0x22 IFLA_BRPORT_MRP_RING_OPEN = 0x23 IFLA_BRPORT_MRP_IN_OPEN = 0x24 + IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT = 0x25 + IFLA_BRPORT_MCAST_EHT_HOSTS_CNT = 0x26 + IFLA_BRPORT_LOCKED = 0x27 + IFLA_BRPORT_MAB = 0x28 + IFLA_BRPORT_MCAST_N_GROUPS = 0x29 + IFLA_BRPORT_MCAST_MAX_GROUPS = 0x2a + IFLA_BRPORT_NEIGH_VLAN_SUPPRESS = 0x2b + IFLA_BRPORT_BACKUP_NHID = 0x2c IFLA_INFO_UNSPEC = 0x0 IFLA_INFO_KIND = 0x1 IFLA_INFO_DATA = 0x2 @@ -1670,6 +1692,9 @@ const ( IFLA_MACVLAN_MACADDR = 0x4 IFLA_MACVLAN_MACADDR_DATA = 0x5 IFLA_MACVLAN_MACADDR_COUNT = 0x6 + IFLA_MACVLAN_BC_QUEUE_LEN = 0x7 + IFLA_MACVLAN_BC_QUEUE_LEN_USED = 0x8 + IFLA_MACVLAN_BC_CUTOFF = 0x9 IFLA_VRF_UNSPEC = 0x0 IFLA_VRF_TABLE = 0x1 IFLA_VRF_PORT_UNSPEC = 0x0 @@ -1693,9 +1718,22 @@ const ( IFLA_XFRM_UNSPEC = 0x0 IFLA_XFRM_LINK = 0x1 IFLA_XFRM_IF_ID = 0x2 + IFLA_XFRM_COLLECT_METADATA = 0x3 IFLA_IPVLAN_UNSPEC = 0x0 IFLA_IPVLAN_MODE = 0x1 IFLA_IPVLAN_FLAGS = 0x2 + NETKIT_NEXT = -0x1 + NETKIT_PASS = 0x0 + NETKIT_DROP = 0x2 + NETKIT_REDIRECT = 0x7 + NETKIT_L2 = 0x0 + NETKIT_L3 = 0x1 + IFLA_NETKIT_UNSPEC = 0x0 + IFLA_NETKIT_PEER_INFO = 0x1 + IFLA_NETKIT_PRIMARY = 0x2 + IFLA_NETKIT_POLICY = 0x3 + IFLA_NETKIT_PEER_POLICY = 0x4 + IFLA_NETKIT_MODE = 0x5 IFLA_VXLAN_UNSPEC = 0x0 IFLA_VXLAN_ID = 0x1 IFLA_VXLAN_GROUP = 0x2 @@ -1726,6 +1764,8 @@ const ( IFLA_VXLAN_GPE = 0x1b IFLA_VXLAN_TTL_INHERIT = 0x1c IFLA_VXLAN_DF = 0x1d + IFLA_VXLAN_VNIFILTER = 0x1e + IFLA_VXLAN_LOCALBYPASS = 0x1f IFLA_GENEVE_UNSPEC = 0x0 IFLA_GENEVE_ID = 0x1 IFLA_GENEVE_REMOTE = 0x2 @@ -1740,6 +1780,7 @@ const ( IFLA_GENEVE_LABEL = 0xb IFLA_GENEVE_TTL_INHERIT = 0xc IFLA_GENEVE_DF = 0xd + IFLA_GENEVE_INNER_PROTO_INHERIT = 0xe IFLA_BAREUDP_UNSPEC = 0x0 IFLA_BAREUDP_PORT = 0x1 IFLA_BAREUDP_ETHERTYPE = 0x2 @@ -1752,6 +1793,8 @@ const ( IFLA_GTP_FD1 = 0x2 IFLA_GTP_PDP_HASHSIZE = 0x3 IFLA_GTP_ROLE = 0x4 + IFLA_GTP_CREATE_SOCKETS = 0x5 + IFLA_GTP_RESTART_COUNT = 0x6 IFLA_BOND_UNSPEC = 0x0 IFLA_BOND_MODE = 0x1 IFLA_BOND_ACTIVE_SLAVE = 0x2 @@ -1781,6 +1824,9 @@ const ( IFLA_BOND_AD_ACTOR_SYSTEM = 0x1a IFLA_BOND_TLB_DYNAMIC_LB = 0x1b IFLA_BOND_PEER_NOTIF_DELAY = 0x1c + IFLA_BOND_AD_LACP_ACTIVE = 0x1d + IFLA_BOND_MISSED_MAX = 0x1e + IFLA_BOND_NS_IP6_TARGET = 0x1f IFLA_BOND_AD_INFO_UNSPEC = 0x0 IFLA_BOND_AD_INFO_AGGREGATOR = 0x1 IFLA_BOND_AD_INFO_NUM_PORTS = 0x2 @@ -1796,6 +1842,7 @@ const ( IFLA_BOND_SLAVE_AD_AGGREGATOR_ID = 0x6 IFLA_BOND_SLAVE_AD_ACTOR_OPER_PORT_STATE = 0x7 IFLA_BOND_SLAVE_AD_PARTNER_OPER_PORT_STATE = 0x8 + IFLA_BOND_SLAVE_PRIO = 0x9 IFLA_VF_INFO_UNSPEC = 0x0 IFLA_VF_INFO = 0x1 IFLA_VF_UNSPEC = 0x0 @@ -1854,8 +1901,16 @@ const ( IFLA_STATS_LINK_XSTATS_SLAVE = 0x3 IFLA_STATS_LINK_OFFLOAD_XSTATS = 0x4 IFLA_STATS_AF_SPEC = 0x5 + IFLA_STATS_GETSET_UNSPEC = 0x0 + IFLA_STATS_GET_FILTERS = 0x1 + IFLA_STATS_SET_OFFLOAD_XSTATS_L3_STATS = 0x2 IFLA_OFFLOAD_XSTATS_UNSPEC = 0x0 IFLA_OFFLOAD_XSTATS_CPU_HIT = 0x1 + IFLA_OFFLOAD_XSTATS_HW_S_INFO = 0x2 + IFLA_OFFLOAD_XSTATS_L3_STATS = 0x3 + IFLA_OFFLOAD_XSTATS_HW_S_INFO_UNSPEC = 0x0 + IFLA_OFFLOAD_XSTATS_HW_S_INFO_REQUEST = 0x1 + IFLA_OFFLOAD_XSTATS_HW_S_INFO_USED = 0x2 IFLA_XDP_UNSPEC = 0x0 IFLA_XDP_FD = 0x1 IFLA_XDP_ATTACHED = 0x2 @@ -1885,6 +1940,11 @@ const ( IFLA_RMNET_UNSPEC = 0x0 IFLA_RMNET_MUX_ID = 0x1 IFLA_RMNET_FLAGS = 0x2 + IFLA_MCTP_UNSPEC = 0x0 + IFLA_MCTP_NET = 0x1 + IFLA_DSA_UNSPEC = 0x0 + IFLA_DSA_CONDUIT = 0x1 + IFLA_DSA_MASTER = 0x1 ) const ( diff --git a/vendor/modules.txt b/vendor/modules.txt index 566623741..a02efe4f5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -40,7 +40,7 @@ github.com/fsmiamoto/git-todo-parser/todo # github.com/gdamore/encoding v1.0.0 ## explicit; go 1.9 github.com/gdamore/encoding -# github.com/gdamore/tcell/v2 v2.7.3 +# github.com/gdamore/tcell/v2 v2.7.4 ## explicit; go 1.12 github.com/gdamore/tcell/v2 github.com/gdamore/tcell/v2/terminfo @@ -172,7 +172,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.20240303173746-f2b0f1f68dd8 +# github.com/jesseduffield/gocui v0.3.1-0.20240309085756-86e0d5a312de ## explicit; go 1.12 github.com/jesseduffield/gocui # github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 @@ -313,13 +313,13 @@ golang.org/x/exp/slices golang.org/x/net/context golang.org/x/net/internal/socks golang.org/x/net/proxy -# golang.org/x/sys v0.17.0 +# golang.org/x/sys v0.18.0 ## explicit; go 1.18 golang.org/x/sys/cpu golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/term v0.17.0 +# golang.org/x/term v0.18.0 ## explicit; go 1.18 golang.org/x/term # golang.org/x/text v0.14.0 From cede0214009888e32027beb212b63e30e77d29e7 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 19 Nov 2023 16:39:01 +0100 Subject: [PATCH 2/6] Add config for soft-wrapping the commit message body --- docs/Config.md | 2 ++ pkg/config/user_config.go | 8 +++++++- pkg/gui/views.go | 2 ++ schema/config.json | 10 ++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/Config.md b/docs/Config.md index 5b686fc19..4b26c5f84 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -92,6 +92,8 @@ git: useConfig: false commit: signOff: false + autoWrapCommitMessage: true # automatic WYSIWYG wrapping of the commit message as you type + autoWrapWidth: 72 # if autoWrapCommitMessage is true, the width to wrap to merging: # only applicable to unix users manualCommit: false diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index 9cb758259..6a4efe78a 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -236,6 +236,10 @@ type PagingConfig struct { type CommitConfig struct { // If true, pass '--signoff' flag when committing SignOff bool `yaml:"signOff"` + // Automatic WYSIWYG wrapping of the commit message as you type + AutoWrapCommitMessage bool `yaml:"autoWrapCommitMessage"` + // If autoWrapCommitMessage is true, the width to wrap to + AutoWrapWidth int `yaml:"autoWrapWidth"` } type MergingConfig struct { @@ -658,7 +662,9 @@ func GetDefaultConfig() *UserConfig { ExternalDiffCommand: "", }, Commit: CommitConfig{ - SignOff: false, + SignOff: false, + AutoWrapCommitMessage: true, + AutoWrapWidth: 72, }, Merging: MergingConfig{ ManualCommit: false, diff --git a/pkg/gui/views.go b/pkg/gui/views.go index 13caa9c7f..9fd775764 100644 --- a/pkg/gui/views.go +++ b/pkg/gui/views.go @@ -168,6 +168,8 @@ func (gui *Gui) createAllViews() error { gui.Views.CommitDescription.FgColor = theme.GocuiDefaultTextColor gui.Views.CommitDescription.Editable = true gui.Views.CommitDescription.Editor = gocui.EditorFunc(gui.commitDescriptionEditor) + gui.Views.CommitDescription.TextArea.AutoWrap = gui.c.UserConfig.Git.Commit.AutoWrapCommitMessage + gui.Views.CommitDescription.TextArea.AutoWrapWidth = gui.c.UserConfig.Git.Commit.AutoWrapWidth gui.Views.Confirmation.Visible = false gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.promptEditor) diff --git a/schema/config.json b/schema/config.json index b95381302..44d8e0cb4 100644 --- a/schema/config.json +++ b/schema/config.json @@ -405,6 +405,16 @@ "signOff": { "type": "boolean", "description": "If true, pass '--signoff' flag when committing" + }, + "autoWrapCommitMessage": { + "type": "boolean", + "description": "Automatic WYSIWYG wrapping of the commit message as you type", + "default": true + }, + "autoWrapWidth": { + "type": "integer", + "description": "If autoWrapCommitMessage is true, the width to wrap to", + "default": 72 } }, "additionalProperties": false, From 379a6f1922aaa9f2eb7ec27a8818fbacd1db0057 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 20 Dec 2023 13:01:41 +0100 Subject: [PATCH 3/6] Save and restore the unwrapped description When preserving the commit message (when cancelling a commit), and later restoring it, use the unwrapped description. --- pkg/gui/controllers.go | 4 +++ .../controllers/commit_message_controller.go | 2 +- pkg/gui/controllers/helpers/commits_helper.go | 29 ++++++++++--------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/pkg/gui/controllers.go b/pkg/gui/controllers.go index 30caae930..1dbf9b7d7 100644 --- a/pkg/gui/controllers.go +++ b/pkg/gui/controllers.go @@ -38,10 +38,14 @@ func (gui *Gui) resetHelpersAndControllers() { getCommitDescription := func() string { return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent()) } + getUnwrappedCommitDescription := func() string { + return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetUnwrappedContent()) + } commitsHelper := helpers.NewCommitsHelper(helperCommon, getCommitSummary, setCommitSummary, getCommitDescription, + getUnwrappedCommitDescription, setCommitDescription, ) diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go index c52a8038f..ef95dffe0 100644 --- a/pkg/gui/controllers/commit_message_controller.go +++ b/pkg/gui/controllers/commit_message_controller.go @@ -100,7 +100,7 @@ func (self *CommitMessageController) handleCommitIndexChange(value int) error { self.c.Helpers().Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage()) return nil } else if currentIndex == context.NoCommitIndex { - self.context().SetHistoryMessage(self.c.Helpers().Commits.JoinCommitMessageAndDescription()) + self.context().SetHistoryMessage(self.c.Helpers().Commits.JoinCommitMessageAndUnwrappedDescription()) } validCommit, err := self.setCommitMessageAtIndex(newIndex) diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index 8691518cd..cd90790a3 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -16,10 +16,11 @@ type ICommitsHelper interface { type CommitsHelper struct { c *HelperCommon - getCommitSummary func() string - setCommitSummary func(string) - getCommitDescription func() string - setCommitDescription func(string) + getCommitSummary func() string + setCommitSummary func(string) + getCommitDescription func() string + getUnwrappedCommitDescription func() string + setCommitDescription func(string) } var _ ICommitsHelper = &CommitsHelper{} @@ -29,14 +30,16 @@ func NewCommitsHelper( getCommitSummary func() string, setCommitSummary func(string), getCommitDescription func() string, + getUnwrappedCommitDescription func() string, setCommitDescription func(string), ) *CommitsHelper { return &CommitsHelper{ - c: c, - getCommitSummary: getCommitSummary, - setCommitSummary: setCommitSummary, - getCommitDescription: getCommitDescription, - setCommitDescription: setCommitDescription, + c: c, + getCommitSummary: getCommitSummary, + setCommitSummary: setCommitSummary, + getCommitDescription: getCommitDescription, + getUnwrappedCommitDescription: getUnwrappedCommitDescription, + setCommitDescription: setCommitDescription, } } @@ -53,11 +56,11 @@ func (self *CommitsHelper) SetMessageAndDescriptionInView(message string) { self.c.Contexts().CommitMessage.RenderCommitLength() } -func (self *CommitsHelper) JoinCommitMessageAndDescription() string { - if len(self.getCommitDescription()) == 0 { +func (self *CommitsHelper) JoinCommitMessageAndUnwrappedDescription() string { + if len(self.getUnwrappedCommitDescription()) == 0 { return self.getCommitSummary() } - return self.getCommitSummary() + "\n" + self.getCommitDescription() + return self.getCommitSummary() + "\n" + self.getUnwrappedCommitDescription() } func (self *CommitsHelper) SwitchToEditor() error { @@ -154,7 +157,7 @@ func (self *CommitsHelper) HandleCommitConfirm() error { func (self *CommitsHelper) CloseCommitMessagePanel() error { if self.c.Contexts().CommitMessage.GetPreserveMessage() { - message := self.JoinCommitMessageAndDescription() + message := self.JoinCommitMessageAndUnwrappedDescription() self.c.Contexts().CommitMessage.SetPreservedMessage(message) } else { From 944d82028faed05b3b53e630c9fcf0c4747891e0 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 20 Dec 2023 18:27:01 +0100 Subject: [PATCH 4/6] Replace DOS linefeeds with Unix line feeds when loading a commit message I have seen some commit messages that contain CRLF instead of just LF; I'm not sure if these were created by a broken git client, but they exist, so we need to deal with them. Editing them when rewording a commit sort of works, but is a little strange; the \r characters are invisble, so you need an extra arrow key press to skip over them. In the next commit we are going to add more logic related to line breaks, and it is getting confused by the \r, so it is becoming more important to fix this. The easiest fix is to normalize the line endings right after loading. --- pkg/commands/git_commands/commit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go index 12806dafc..960fab811 100644 --- a/pkg/commands/git_commands/commit.go +++ b/pkg/commands/git_commands/commit.go @@ -142,7 +142,7 @@ func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) { ToArgv() message, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput() - return strings.TrimSpace(message), err + return strings.ReplaceAll(strings.TrimSpace(message), "\r\n", "\n"), err } func (self *CommitCommands) GetCommitSubject(commitSha string) (string, error) { From 41a68f7c4afcda3479b13bf8bd3e63b4906054b8 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Wed, 20 Dec 2023 19:17:42 +0100 Subject: [PATCH 5/6] Remove hard line breaks when rewording commits ... and when recalling a commit message from an old commit by pressing up-arrow. This is necessary because committing turns our soft line breaks into real ones, but when rewording we want to turn them back into soft ones again, so that it's possible to insert words at the beginning of a paragraph and have everything rewrap nicely. This is only a best effort; the algorithm only removes those hard line breaks that can be removed without changing the way the message looks. This works well when the previous commit message was wrapped at the same width, which for most users should be the most common case; but if it wasn't, the result is not great. Specifically, if the old wrap width was smaller, some hard line breaks just won't be removed; if it was wider though, you'll get an unpleasant comb effect with alternating long and short lines. In such a case it's best to switch to the editor and use whatever wrapping features you have there (e.g. alt-Q). --- .../controllers/commit_message_controller.go | 4 ++ pkg/gui/controllers/helpers/commits_helper.go | 26 ++++++++++++ .../helpers/commits_helper_test.go | 41 +++++++++++++++++++ .../controllers/local_commits_controller.go | 4 +- 4 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 pkg/gui/controllers/helpers/commits_helper_test.go diff --git a/pkg/gui/controllers/commit_message_controller.go b/pkg/gui/controllers/commit_message_controller.go index ef95dffe0..756b240e6 100644 --- a/pkg/gui/controllers/commit_message_controller.go +++ b/pkg/gui/controllers/commit_message_controller.go @@ -3,6 +3,7 @@ package controllers import ( "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/gui/context" + "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -119,6 +120,9 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e } return false, self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr) } + if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage { + commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth) + } self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage) return true, nil } diff --git a/pkg/gui/controllers/helpers/commits_helper.go b/pkg/gui/controllers/helpers/commits_helper.go index cd90790a3..0801d5742 100644 --- a/pkg/gui/controllers/helpers/commits_helper.go +++ b/pkg/gui/controllers/helpers/commits_helper.go @@ -5,6 +5,7 @@ import ( "strings" "time" + "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/samber/lo" ) @@ -63,6 +64,31 @@ func (self *CommitsHelper) JoinCommitMessageAndUnwrappedDescription() string { return self.getCommitSummary() + "\n" + self.getUnwrappedCommitDescription() } +func TryRemoveHardLineBreaks(message string, autoWrapWidth int) string { + messageRunes := []rune(message) + lastHardLineStart := 0 + for i, r := range messageRunes { + if r == '\n' { + // Try to make this a soft linebreak by turning it into a space, and + // checking whether it still wraps to the same result then. + messageRunes[i] = ' ' + + _, cursorMapping := gocui.AutoWrapContent(messageRunes[lastHardLineStart:], autoWrapWidth) + + // Look at the cursorMapping to check whether auto-wrapping inserted + // a line break. If it did, there will be a cursorMapping entry with + // Orig pointing to the position after the inserted line break. + if len(cursorMapping) == 0 || cursorMapping[0].Orig != i-lastHardLineStart+1 { + // It didn't, so change it back to a newline + messageRunes[i] = '\n' + } + lastHardLineStart = i + 1 + } + } + + return string(messageRunes) +} + func (self *CommitsHelper) SwitchToEditor() error { if !self.c.Contexts().CommitMessage.CanSwitchToEditor() { return nil diff --git a/pkg/gui/controllers/helpers/commits_helper_test.go b/pkg/gui/controllers/helpers/commits_helper_test.go new file mode 100644 index 000000000..6197c3916 --- /dev/null +++ b/pkg/gui/controllers/helpers/commits_helper_test.go @@ -0,0 +1,41 @@ +package helpers + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTryRemoveHardLineBreaks(t *testing.T) { + scenarios := []struct { + name string + message string + autoWrapWidth int + expectedResult string + }{ + { + name: "empty", + message: "", + autoWrapWidth: 7, + expectedResult: "", + }, + { + name: "all line breaks are needed", + message: "abc\ndef\n\nxyz", + autoWrapWidth: 7, + expectedResult: "abc\ndef\n\nxyz", + }, + { + name: "some can be unwrapped", + message: "123\nabc def\nghi jkl\nmno\n456\n", + autoWrapWidth: 7, + expectedResult: "123\nabc def ghi jkl mno\n456\n", + }, + } + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + actualResult := TryRemoveHardLineBreaks(s.message, s.autoWrapWidth) + assert.Equal(t, s.expectedResult, actualResult) + }) + } +} diff --git a/pkg/gui/controllers/local_commits_controller.go b/pkg/gui/controllers/local_commits_controller.go index ef6b5be80..8c99d7586 100644 --- a/pkg/gui/controllers/local_commits_controller.go +++ b/pkg/gui/controllers/local_commits_controller.go @@ -354,7 +354,9 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error { if err != nil { return self.c.Error(err) } - + if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage { + commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth) + } return self.c.Helpers().Commits.OpenCommitMessagePanel( &helpers.OpenCommitMessagePanelOpts{ CommitIndex: self.context().GetSelectedLineIdx(), From d1f8c450995ace6b1b0e352ba10dcc0653da4030 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sat, 20 Jan 2024 17:55:25 +0100 Subject: [PATCH 6/6] Add integration test --- .../commit_description_panel_driver.go | 10 ++++ .../tests/commit/auto_wrap_message.go | 59 +++++++++++++++++++ pkg/integration/tests/test_list.go | 1 + 3 files changed, 70 insertions(+) create mode 100644 pkg/integration/tests/commit/auto_wrap_message.go diff --git a/pkg/integration/components/commit_description_panel_driver.go b/pkg/integration/components/commit_description_panel_driver.go index 0c4b2cfbb..eddc53533 100644 --- a/pkg/integration/components/commit_description_panel_driver.go +++ b/pkg/integration/components/commit_description_panel_driver.go @@ -31,6 +31,16 @@ func (self *CommitDescriptionPanelDriver) AddNewline() *CommitDescriptionPanelDr return self } +func (self *CommitDescriptionPanelDriver) GoToBeginning() *CommitDescriptionPanelDriver { + numLines := len(self.getViewDriver().getView().BufferLines()) + for i := 0; i < numLines; i++ { + self.t.pressFast("") + } + + self.t.pressFast("") + return self +} + func (self *CommitDescriptionPanelDriver) Title(expected *TextMatcher) *CommitDescriptionPanelDriver { self.getViewDriver().Title(expected) diff --git a/pkg/integration/tests/commit/auto_wrap_message.go b/pkg/integration/tests/commit/auto_wrap_message.go new file mode 100644 index 000000000..477bfae62 --- /dev/null +++ b/pkg/integration/tests/commit/auto_wrap_message.go @@ -0,0 +1,59 @@ +package commit + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var AutoWrapMessage = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Commit, and test how the commit message body is auto-wrapped", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + // Use a ridiculously small width so that we don't have to use so much test data + config.UserConfig.Git.Commit.AutoWrapWidth = 20 + }, + SetupRepo: func(shell *Shell) { + shell.CreateFile("file", "file content") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Commits(). + IsEmpty() + + t.Views().Files(). + IsFocused(). + PressPrimaryAction(). // stage file + Press(keys.Files.CommitChanges) + + t.ExpectPopup().CommitMessagePanel(). + Type("subject"). + SwitchToDescription(). + Type("Lorem ipsum dolor sit amet, consectetur adipiscing elit."). + // See how it automatically inserted line feeds to wrap the text: + Content(Equals("Lorem ipsum dolor \nsit amet, \nconsectetur \nadipiscing elit.")). + SwitchToSummary(). + Confirm() + + t.Views().Commits(). + Lines( + Contains("subject"), + ). + Focus(). + Tap(func() { + t.Views().Main().Content(Contains( + "subject\n \n Lorem ipsum dolor\n sit amet,\n consectetur\n adipiscing elit.")) + }). + Press(keys.Commits.RenameCommit) + + // Test that when rewording, the hard line breaks are turned back into + // soft ones, so that we can insert text at the beginning and have the + // paragraph reflow nicely. + t.ExpectPopup().CommitMessagePanel(). + InitialText(Equals("subject")). + SwitchToDescription(). + Content(Equals("Lorem ipsum dolor \nsit amet, \nconsectetur \nadipiscing elit.")). + GoToBeginning(). + Type("More text. "). + Content(Equals("More text. Lorem \nipsum dolor sit \namet, consectetur \nadipiscing elit.")) + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index 402a40acf..8c2891d47 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -65,6 +65,7 @@ var tests = []*components.IntegrationTest{ cherry_pick.CherryPickRange, commit.AddCoAuthor, commit.Amend, + commit.AutoWrapMessage, commit.Commit, commit.CommitMultiline, commit.CommitSwitchToEditor,