Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package lazygit for openSUSE:Factory checked in at 2026-02-17 16:51:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/lazygit (Old) and /work/SRC/openSUSE:Factory/.lazygit.new.1977 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "lazygit" Tue Feb 17 16:51:32 2026 rev:14 rq:1333471 version:0.59.0 Changes: -------- --- /work/SRC/openSUSE:Factory/lazygit/lazygit.changes 2026-01-05 14:55:14.691482224 +0100 +++ /work/SRC/openSUSE:Factory/.lazygit.new.1977/lazygit.changes 2026-02-17 16:53:14.398209316 +0100 @@ -1,0 +2,46 @@ +Sun Feb 08 15:01:55 UTC 2026 - [email protected] + +- Update to version 0.59.0: + * Update docs and schema for release + * Auto-update cheatsheets after updating translation + * Update translations from Crowdin + * fix(config): add Nushell support for nvim-remote preset + * Update README to mention GitArbor as an alternative + * Disable the automatic, scheduled release + * Fix rendering of the Reflog panel when using up/down to scroll it + * Fix gitignore path collisions + * Bump gocui + * When moving commits up/down and the selection leaves the view, scroll to make it visible again + * Support using the selected commit's message in a fixup + * Pass todo flag to EditRebaseTodo + * Show -C for fixup todos in rebase if present + * Add ActionFlag field to Commit + * Remove the "old cherry-pick key" warning + * Limit popup panel widths to a maximum width + * Fix opening a menu when a previous menu was scrolled down + * Don't log the "git rev-list" call when marking bisect commits + * Bump gocui + * chore: Update outdated GitHub Actions versions + * Ignore fixup commits for a found base commit when doing ctrl-f + * Extract a helper method getCommitsForHashes + * Make getHashesAndSubjects a free-standing function + * Move isFixupCommit to FixupHelper + * Add a test that documents the current behavior + * README.md: Update Sponsors + * Update devcontainer `VARIANT` to `1-trixie` + +------------------------------------------------------------------- +Sun Feb 08 15:01:27 UTC 2026 - [email protected] + +- Update to version 0.58.1: + * Bump tcell + * Don't log the "git ls-remote" call when opening a PR + * Ask users to provide information about their terminal + * Remove git version + * bump tcell + * Bump gocui + * When doing ctrl-f, and the resulting commit is not visible, scroll it into view + * Update search position (match x of y) when changing the selection in a list view + * Bump gocui and adapt code accordingly + +------------------------------------------------------------------- Old: ---- lazygit-0.58.0.tar.gz New: ---- lazygit-0.59.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ lazygit.spec ++++++ --- /var/tmp/diff_new_pack.OfNAbC/_old 2026-02-17 16:53:19.046403328 +0100 +++ /var/tmp/diff_new_pack.OfNAbC/_new 2026-02-17 16:53:19.046403328 +0100 @@ -17,7 +17,7 @@ Name: lazygit -Version: 0.58.0 +Version: 0.59.0 Release: 0 Summary: Simple terminal UI for git commands License: MIT ++++++ _service ++++++ --- /var/tmp/diff_new_pack.OfNAbC/_old 2026-02-17 16:53:19.094405332 +0100 +++ /var/tmp/diff_new_pack.OfNAbC/_new 2026-02-17 16:53:19.098405499 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/jesseduffield/lazygit.git</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v0.58.0</param> + <param name="revision">v0.59.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.OfNAbC/_old 2026-02-17 16:53:19.150407669 +0100 +++ /var/tmp/diff_new_pack.OfNAbC/_new 2026-02-17 16:53:19.154407836 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/jesseduffield/lazygit.git</param> - <param name="changesrevision">a0e81c93b89ccb13efe186d057505c624fb1e4ec</param></service></servicedata> + <param name="changesrevision">1d0db51caf3d280a53f027ef049355fc9e0c57e8</param></service></servicedata> (No newline at EOF) ++++++ lazygit-0.58.0.tar.gz -> lazygit-0.59.0.tar.gz ++++++ ++++ 3482 lines of diff (skipped) ++++++ vendor.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/gdamore/tcell/v2/input.go new/vendor/github.com/gdamore/tcell/v2/input.go --- old/vendor/github.com/gdamore/tcell/v2/input.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/github.com/gdamore/tcell/v2/input.go 2026-02-08 16:01:57.000000000 +0100 @@ -84,6 +84,7 @@ evch chan<- Event rows int // used for clipping mouse coordinates cols int // used for clipping mouse coordinates + surrogate rune nested *inputProcessor } @@ -135,8 +136,9 @@ } type keyMap struct { - Key Key - Mod ModMask + Key Key + Mod ModMask + Rune rune } var csiAllKeys = map[csiParamMode]keyMap{ @@ -275,41 +277,70 @@ } // keys reported using Kitty csi-u protocol -var csiUKeys = map[int]Key{ - 27: KeyESC, - 9: KeyTAB, - 13: KeyEnter, - 127: KeyBS, - 57358: KeyCapsLock, - 57359: KeyScrollLock, - 57360: KeyNumLock, - 57361: KeyPrint, - 57362: KeyPause, - 57363: KeyMenu, - 57376: KeyF13, - 57377: KeyF14, - 57378: KeyF15, - 57379: KeyF16, - 57380: KeyF17, - 57381: KeyF18, - 57382: KeyF19, - 57383: KeyF20, - 57384: KeyF21, - 57385: KeyF22, - 57386: KeyF23, - 57387: KeyF24, - 57388: KeyF25, - 57389: KeyF26, - 57390: KeyF27, - 57391: KeyF28, - 57392: KeyF29, - 57393: KeyF30, - 57394: KeyF31, - 57395: KeyF32, - 57396: KeyF33, - 57397: KeyF34, - 57398: KeyF35, - // TODO: KP keys +var csiUKeys = map[int]keyMap{ + 27: {Key: KeyESC}, + 9: {Key: KeyTAB}, + 13: {Key: KeyEnter}, + 127: {Key: KeyBS}, + 57358: {Key: KeyCapsLock}, + 57359: {Key: KeyScrollLock}, + 57360: {Key: KeyNumLock}, + 57361: {Key: KeyPrint}, + 57362: {Key: KeyPause}, + 57363: {Key: KeyMenu}, + 57376: {Key: KeyF13}, + 57377: {Key: KeyF14}, + 57378: {Key: KeyF15}, + 57379: {Key: KeyF16}, + 57380: {Key: KeyF17}, + 57381: {Key: KeyF18}, + 57382: {Key: KeyF19}, + 57383: {Key: KeyF20}, + 57384: {Key: KeyF21}, + 57385: {Key: KeyF22}, + 57386: {Key: KeyF23}, + 57387: {Key: KeyF24}, + 57388: {Key: KeyF25}, + 57389: {Key: KeyF26}, + 57390: {Key: KeyF27}, + 57391: {Key: KeyF28}, + 57392: {Key: KeyF29}, + 57393: {Key: KeyF30}, + 57394: {Key: KeyF31}, + 57395: {Key: KeyF32}, + 57396: {Key: KeyF33}, + 57397: {Key: KeyF34}, + 57398: {Key: KeyF35}, + 57399: {Key: KeyRune, Rune: '0'}, // KP 0 + 57400: {Key: KeyRune, Rune: '1'}, // KP 1 + 57401: {Key: KeyRune, Rune: '2'}, // KP 2 + 57402: {Key: KeyRune, Rune: '3'}, // KP 3 + 57403: {Key: KeyRune, Rune: '4'}, // KP 4 + 57404: {Key: KeyRune, Rune: '5'}, // KP 5 + 57405: {Key: KeyRune, Rune: '6'}, // KP 6 + 57406: {Key: KeyRune, Rune: '7'}, // KP 7 + 57407: {Key: KeyRune, Rune: '8'}, // KP 8 + 57408: {Key: KeyRune, Rune: '9'}, // KP 9 + 57409: {Key: KeyRune, Rune: '.'}, // KP_DECIMAL + 57410: {Key: KeyRune, Rune: '/'}, // KP_DIVIDE + 57411: {Key: KeyRune, Rune: '*'}, // KP_MULTIPLY + 57412: {Key: KeyRune, Rune: '-'}, // KP_SUBTRACT + 57413: {Key: KeyRune, Rune: '+'}, // KP_ADD + 57414: {Key: KeyEnter}, // KP_ENTER + 57415: {Key: KeyRune, Rune: '='}, // KP_EQUAL + 57416: {Key: KeyClear}, // KP_SEPARATOR + 57417: {Key: KeyLeft}, // KP_LEFT + 57418: {Key: KeyRight}, // KP_RIGHT + 57419: {Key: KeyUp}, // KP_UP + 57420: {Key: KeyDown}, // KP_DOWN + 57421: {Key: KeyPgUp}, // KP_PG_UP + 57422: {Key: KeyPgDn}, // KP_PG_DN + 57423: {Key: KeyHome}, // KP_HOME + 57424: {Key: KeyEnd}, // KP_END + 57425: {Key: KeyInsert}, // KP_INSERT + 57426: {Key: KeyDelete}, // KP_DELETE + // 57427: {Key: KeyBegin}, // KP_BEGIN + // TODO: Media keys } @@ -318,8 +349,8 @@ 0x03: KeyCancel, // vkCancel 0x08: KeyBackspace, // vkBackspace 0x09: KeyTab, // vkTab + 0x0c: KeyClear, // vClear 0x0d: KeyEnter, // vkReturn - 0x12: KeyClear, // vClear 0x13: KeyPause, // vkPause 0x1b: KeyEscape, // vkEscape 0x21: KeyPgUp, // vkPrior @@ -701,11 +732,22 @@ } else if chr < ' ' && P[0] >= 0x41 && P[0] <= 0x5a { key = Key(P[0]) chr = 0 - } else if key == 0x11 || key == 0x13 || key == 0x14 { + + } else if chr >= 0xD800 && chr <= 0xDBFF { + // high surrogate pair + ip.surrogate = chr + return + } else if chr >= 0xDC00 && chr <= 0xDFFF { + // low surrogate pair + chr = utf16.DecodeRune(ip.surrogate, chr) + } else if P[0] == 0x10 || P[0] == 0x11 || P[0] == 0x12 || P[0] == 0x14 { // lone modifiers + ip.surrogate = 0 return } + ip.surrogate = 0 + // Modifiers if P[4]&0x010 != 0 { mod |= ModShift @@ -791,8 +833,8 @@ key := KeyRune chr := rune(0) if k1, ok := csiUKeys[P0]; ok { - key = k1 - chr = 0 + key = k1.Key + chr = k1.Rune } else { chr = rune(P0) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/gdamore/tcell/v2/tscreen.go new/vendor/github.com/gdamore/tcell/v2/tscreen.go --- old/vendor/github.com/gdamore/tcell/v2/tscreen.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/github.com/gdamore/tcell/v2/tscreen.go 2026-02-08 16:01:57.000000000 +0100 @@ -622,6 +622,14 @@ width = 1 str = " " } + if width > 1 && x+width < t.w { + // Clobber over any content in the next cell. + // This fixes a problem with some terminals where overwriting two + // adjacent single cells with a wide rune would leave an image + // of the second cell. This is a workaround for buggy terminals. + t.writeString(" \b\b") + } + t.writeString(str) t.cx += width t.cells.SetDirty(x, y, false) @@ -989,9 +997,14 @@ } func (t *tScreen) scanInput(buf *bytes.Buffer) { + // The end of the buffer isn't necessarily the end of the input, because + // large inputs are chunked. Set atEOF to false so the UTF-8 validating decoder + // returns ErrShortSrc instead of ErrInvalidUTF8 for incomplete multi-byte codepoints. + const atEOF = false + for buf.Len() > 0 { utf := make([]byte, min(8, max(buf.Len()*2, 128))) - nOut, nIn, e := t.decoder.Transform(utf, buf.Bytes(), true) + nOut, nIn, e := t.decoder.Transform(utf, buf.Bytes(), atEOF) _ = buf.Next(nIn) t.input.ScanUTF8(utf[:nOut]) if e == transform.ErrShortSrc { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/gdamore/tcell/v2/tty_win.go new/vendor/github.com/gdamore/tcell/v2/tty_win.go --- old/vendor/github.com/gdamore/tcell/v2/tty_win.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/github.com/gdamore/tcell/v2/tty_win.go 2026-02-08 16:01:57.000000000 +0100 @@ -1,4 +1,4 @@ -// Copyright 2025 The TCell Authors +// Copyright 2026 The TCell Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use file except in compliance with the License. @@ -72,6 +72,7 @@ oomode uint32 // original output mode oscreen consoleInfo wg sync.WaitGroup + surrogate rune sync.Mutex } @@ -126,7 +127,7 @@ func (w *winTty) getConsoleInput() error { // cancelFlag comes first as WaitForMultipleObjects returns the lowest index - // in the event that both events are signalled. + // in the event that both events are signaled. waitObjects := []syscall.Handle{w.cancelFlag, w.in} // As arrays are contiguous in memory, a pointer to the first object is the @@ -158,19 +159,30 @@ if rv == 0 { return er } + loop: for i := range nrec { ir := rec[i] switch ir.typ { case keyEvent: - chr := ir.data[10] // we only see ASCII, key down events in VT mode - - // because we use win32-input-mode, we will only - // see US-ASCII characters - (Q: will they be - // 16-bit values with possible surrogate pairs?) - select { - case w.buf <- chr: - case <-w.stopQ: - break + // we normally only expect to see ascii, but paste data may come in as UTF-16. + wc := rune(binary.LittleEndian.Uint16(ir.data[10:])) + if wc >= 0xD800 && wc <= 0xDBFF { + // if it was a high surrogate, which happens for pasted UTF-16, + // then save it until we get the low and can decode it. + w.surrogate = wc + continue + } else if wc >= 0xDC00 && wc <= 0xDFFF { + wc = utf16.DecodeRune(w.surrogate, wc) + } + w.surrogate = 0 + for _, chr := range []byte(string(wc)) { + // We normally expect only to see ASCII (win32-input-mode), + // but apparently pasted data can arrive in UTF-16 here. + select { + case w.buf <- chr: + case <-w.stopQ: + break loop + } } case resizeEvent: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/jesseduffield/gocui/escape.go new/vendor/github.com/jesseduffield/gocui/escape.go --- old/vendor/github.com/jesseduffield/gocui/escape.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/github.com/jesseduffield/gocui/escape.go 2026-02-08 16:01:57.000000000 +0100 @@ -39,6 +39,7 @@ const ( stateNone escapeState = iota stateEscape + stateCharacterSetDesignation stateCSI stateParams stateOSC @@ -144,9 +145,19 @@ case characterEquals(ch, ']'): ei.state = stateOSC return true, nil + case characterEquals(ch, '('), + characterEquals(ch, ')'), + characterEquals(ch, '*'), + characterEquals(ch, '+'): + ei.state = stateCharacterSetDesignation + return true, nil default: return false, errNotCSI } + case stateCharacterSetDesignation: + // Not supported, so just skip it + ei.state = stateNone + return true, nil case stateCSI: switch { case len(ch) == 1 && ch[0] >= '0' && ch[0] <= '9': diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/jesseduffield/gocui/gui.go new/vendor/github.com/jesseduffield/gocui/gui.go --- old/vendor/github.com/jesseduffield/gocui/gui.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/github.com/jesseduffield/gocui/gui.go 2026-02-08 16:01:57.000000000 +0100 @@ -1313,9 +1313,9 @@ // 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'. + // KeyCtrlJ (int value 10) is '\r'. if g.IsPasting && ev.Key == KeyCtrlJ { - ev.Key = KeyCtrlM + ev.Key = KeyEnter } err := g.execKeybindings(g.currentView, ev) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/jesseduffield/gocui/text_area.go new/vendor/github.com/jesseduffield/gocui/text_area.go --- old/vendor/github.com/jesseduffield/gocui/text_area.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/github.com/jesseduffield/gocui/text_area.go 2026-02-08 16:01:57.000000000 +0100 @@ -71,6 +71,7 @@ currentLineWidth := 0 indexOfLastWhitespace := -1 var footNoteMatcher footNoteMatcher + var trailerMatcher trailerMatcher cells := stringToTextAreaCells(content) y := 0 @@ -94,9 +95,10 @@ indexOfLastWhitespace = -1 currentLineWidth = 0 footNoteMatcher.reset() + trailerMatcher.reset() } else { currentLineWidth += c.width - if c.char == " " && !footNoteMatcher.isFootNote() { + if c.char == " " && !footNoteMatcher.isFootNote() && !trailerMatcher.isTrailer() { indexOfLastWhitespace = currentPos + 1 } else if autoWrapWidth > 0 && currentLineWidth > autoWrapWidth && indexOfLastWhitespace >= 0 { wrapAt := indexOfLastWhitespace @@ -112,9 +114,11 @@ currentLineWidth += c1.width } footNoteMatcher.reset() + trailerMatcher.reset() } footNoteMatcher.addCharacter(c.char) + trailerMatcher.addCharacter(c.char) } } @@ -167,6 +171,76 @@ self.didFailToMatch = false } +var supportedTrailers = []string{ + "Signed-off-by:", + "Co-authored-by:", +} + +type trailerMatcher struct { + lineStr strings.Builder + didFailToMatch bool + didMatch bool +} + +func (self *trailerMatcher) addCharacter(chr string) { + if self.didFailToMatch || self.didMatch { + return + } + + if len(chr) != 1 { + // Trailers are all ASCII, so if we get a non-ASCII UTF-8 character (or even a multi-rune + // grapheme cluster), we can fail early. + self.didFailToMatch = true + return + } + + if self.lineStr.Len() == 0 { + // If this is the first character, see if it could possibly match any supported trailer; if + // not, we can fail early and stop tracking further characters for this line. + if !anyOf(supportedTrailers, func(trailer string) bool { return trailer[0] == chr[0] }) { + self.didFailToMatch = true + return + } + } + + self.lineStr.WriteString(chr) +} + +func (self *trailerMatcher) isTrailer() bool { + if self.didFailToMatch { + return false + } + + if self.didMatch { + return true + } + + line := self.lineStr.String() + if anyOf(supportedTrailers, func(trailer string) bool { return line == trailer }) { + self.didMatch = true + return true + } + + self.didFailToMatch = true + return false +} + +func (self *trailerMatcher) reset() { + self.lineStr.Reset() + self.didFailToMatch = false + self.didMatch = false +} + +func anyOf(strings []string, predicate func(s string) bool) bool { + for _, s := range strings { + if predicate(s) { + return true + } + } + + return false +} + func (self *TextArea) updateCells() { width := self.AutoWrapWidth if !self.AutoWrap { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/github.com/jesseduffield/gocui/view.go new/vendor/github.com/jesseduffield/gocui/view.go --- old/vendor/github.com/jesseduffield/gocui/view.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/github.com/jesseduffield/gocui/view.go 2026-02-08 16:01:57.000000000 +0100 @@ -167,9 +167,6 @@ // If HasLoader is true, the message will be appended with a spinning loader animation HasLoader bool - // IgnoreCarriageReturns tells us whether to ignore '\r' characters - IgnoreCarriageReturns bool - // ParentView is the view which catches events bubbled up from the given view if there's no matching handler ParentView *View @@ -217,29 +214,53 @@ searchPositions []SearchPosition modelSearchResults []SearchPosition currentSearchIndex int - onSelectItem func(int, int, int) error + onSelectItem func(int) + renderSearchStatus func(int, int) +} + +func (v *View) SetRenderSearchStatus(renderSearchStatus func(int, int)) { + v.searcher.renderSearchStatus = renderSearchStatus } -func (v *View) SetOnSelectItem(onSelectItem func(int, int, int) error) { +func (v *View) SetOnSelectItem(onSelectItem func(int)) { v.searcher.onSelectItem = onSelectItem } +func (v *View) renderSearchStatus(index int, itemCount int) { + if v.searcher.renderSearchStatus != nil { + v.searcher.renderSearchStatus(index, itemCount) + } +} + func (v *View) gotoNextMatch() error { if len(v.searcher.searchPositions) == 0 { return nil } + if v.Highlight && v.oy+v.cy < v.searcher.searchPositions[v.searcher.currentSearchIndex].Y { + // If the selection is before the current match, just jump to the current match and return. + // This can only happen if the user has moved the cursor to before the first match. + v.SelectSearchResult(v.searcher.currentSearchIndex) + return nil + } if v.searcher.currentSearchIndex >= len(v.searcher.searchPositions)-1 { v.searcher.currentSearchIndex = 0 } else { v.searcher.currentSearchIndex++ } - return v.SelectSearchResult(v.searcher.currentSearchIndex) + v.SelectSearchResult(v.searcher.currentSearchIndex) + return nil } func (v *View) gotoPreviousMatch() error { if len(v.searcher.searchPositions) == 0 { return nil } + if v.Highlight && v.oy+v.cy > v.searcher.searchPositions[v.searcher.currentSearchIndex].Y { + // If the selection is after the current match, just jump to the current match and return. + // This happens if the user has moved the cursor down from the current match. + v.SelectSearchResult(v.searcher.currentSearchIndex) + return nil + } if v.searcher.currentSearchIndex == 0 { if len(v.searcher.searchPositions) > 0 { v.searcher.currentSearchIndex = len(v.searcher.searchPositions) - 1 @@ -247,13 +268,14 @@ } else { v.searcher.currentSearchIndex-- } - return v.SelectSearchResult(v.searcher.currentSearchIndex) + v.SelectSearchResult(v.searcher.currentSearchIndex) + return nil } -func (v *View) SelectSearchResult(index int) error { +func (v *View) SelectSearchResult(index int) { itemCount := len(v.searcher.searchPositions) if itemCount == 0 { - return nil + return } if index > itemCount-1 { index = itemCount - 1 @@ -262,10 +284,10 @@ y := v.searcher.searchPositions[index].Y v.FocusPoint(v.ox, y, true) + v.renderSearchStatus(index, itemCount) if v.searcher.onSelectItem != nil { - return v.searcher.onSelectItem(y, index, itemCount) + v.searcher.onSelectItem(y) } - return nil } // Returns <current match index>, <total matches> @@ -294,26 +316,29 @@ if len(v.searcher.searchPositions) > 0 { // get the first result past the current cursor currentIndex := 0 - adjustedY := v.oy + v.cy - adjustedX := v.ox + v.cx - for i, pos := range v.searcher.searchPositions { - if pos.Y > adjustedY || (pos.Y == adjustedY && pos.XStart > adjustedX) { - currentIndex = i - break + if v.Highlight { + // ...but only if we're showing the highlighted line + adjustedY := v.oy + v.cy + adjustedX := v.ox + v.cx + for i, pos := range v.searcher.searchPositions { + if pos.Y > adjustedY || (pos.Y == adjustedY && pos.XStart > adjustedX) { + currentIndex = i + break + } } } v.searcher.currentSearchIndex = currentIndex } } -func (v *View) Search(str string, modelSearchResults []SearchPosition) error { +func (v *View) Search(str string, modelSearchResults []SearchPosition) { v.UpdateSearchResults(str, modelSearchResults) if len(v.searcher.searchPositions) > 0 { - return v.SelectSearchResult(v.searcher.currentSearchIndex) + v.SelectSearchResult(v.searcher.currentSearchIndex) + } else { + v.renderSearchStatus(0, 0) } - - return v.searcher.onSelectItem(-1, -1, 0) } func (v *View) ClearSearch() { @@ -324,8 +349,37 @@ return v.searcher.searchString != "" } +func (v *View) nearestSearchPosition() int { + currentLineIndex := v.cy + v.oy + lastSearchPos := 0 + for i, pos := range v.searcher.searchPositions { + if pos.Y == currentLineIndex { + return i + } + if pos.Y > currentLineIndex { + break + } + lastSearchPos = i + } + return lastSearchPos +} + +func (v *View) SetNearestSearchPosition() { + if len(v.searcher.searchPositions) > 0 { + newPos := v.nearestSearchPosition() + if newPos != v.searcher.currentSearchIndex { + v.searcher.currentSearchIndex = newPos + v.renderSearchStatus(newPos, len(v.searcher.searchPositions)) + } + } +} + func (v *View) FocusPoint(cx int, cy int, scrollIntoView bool) { - lineCount := len(v.lines) + v.writeMutex.Lock() + defer v.writeMutex.Unlock() + + v.refreshViewLinesIfNeeded() + lineCount := len(v.viewLines) if cy < 0 || cy > lineCount { return } @@ -408,6 +462,10 @@ return len(chr) == 1 && chr[0] == b } +func isCRLF(chr []byte) bool { + return len(chr) == 2 && chr[0] == '\r' && chr[1] == '\n' +} + // String returns a string from a given cell slice. func (l lineType) String() string { var str strings.Builder @@ -783,7 +841,7 @@ chr, remaining, width, state = uniseg.FirstGraphemeCluster(remaining, state) switch { - case characterEquals(chr, '\n'): + case characterEquals(chr, '\n') || isCRLF(chr): finishLine() advanceToNextLine() case characterEquals(chr, '\r'): @@ -1757,6 +1815,52 @@ v.lines = v.lines[:lineCount] } +// If the current search result is no longer visible after a scroll up, select the last search +// result that is visible in the view, if any, or the first one that is below the view if none is +// visible. +func (v *View) selectVisibleSearchResultAfterScrollUp() { + if !v.Highlight && len(v.searcher.searchPositions) != 0 { + windowBottom := v.oy + v.InnerHeight() + if v.searcher.searchPositions[v.searcher.currentSearchIndex].Y >= windowBottom { + newSearchIndex := v.searcher.currentSearchIndex + for newSearchIndex > 0 && + v.searcher.searchPositions[newSearchIndex-1].Y >= v.oy { + newSearchIndex-- + if v.searcher.searchPositions[newSearchIndex].Y < windowBottom { + break + } + } + if v.searcher.currentSearchIndex != newSearchIndex { + v.searcher.currentSearchIndex = newSearchIndex + v.renderSearchStatus(newSearchIndex, len(v.searcher.searchPositions)) + } + } + } +} + +// If the current search result is no longer visible after a scroll down, select the first search +// result that is visible in the view, if any, or the last one that is above the view if none is +// visible. +func (v *View) selectVisibleSearchResultAfterScrollDown() { + if !v.Highlight && len(v.searcher.searchPositions) != 0 { + if v.searcher.searchPositions[v.searcher.currentSearchIndex].Y < v.oy { + newSearchIndex := v.searcher.currentSearchIndex + windowBottom := v.oy + v.InnerHeight() + for newSearchIndex+1 < len(v.searcher.searchPositions) && + v.searcher.searchPositions[newSearchIndex+1].Y < windowBottom { + newSearchIndex++ + if v.searcher.searchPositions[newSearchIndex].Y >= v.oy { + break + } + } + if v.searcher.currentSearchIndex != newSearchIndex { + v.searcher.currentSearchIndex = newSearchIndex + v.renderSearchStatus(newSearchIndex, len(v.searcher.searchPositions)) + } + } + } +} + func (v *View) ScrollUp(amount int) { if amount > v.oy { amount = v.oy @@ -1767,6 +1871,7 @@ v.cy += amount v.clearHover() + v.selectVisibleSearchResultAfterScrollUp() } } @@ -1778,6 +1883,7 @@ v.cy -= adjustedAmount v.clearHover() + v.selectVisibleSearchResultAfterScrollDown() } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/sys/cpu/cpu_x86.go new/vendor/golang.org/x/sys/cpu/cpu_x86.go --- old/vendor/golang.org/x/sys/cpu/cpu_x86.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/golang.org/x/sys/cpu/cpu_x86.go 2026-02-08 16:01:57.000000000 +0100 @@ -64,6 +64,80 @@ func archInit() { + // From internal/cpu + const ( + // eax bits + cpuid_AVXVNNI = 1 << 4 + + // ecx bits + cpuid_SSE3 = 1 << 0 + cpuid_PCLMULQDQ = 1 << 1 + cpuid_AVX512VBMI = 1 << 1 + cpuid_AVX512VBMI2 = 1 << 6 + cpuid_SSSE3 = 1 << 9 + cpuid_AVX512GFNI = 1 << 8 + cpuid_AVX512VAES = 1 << 9 + cpuid_AVX512VNNI = 1 << 11 + cpuid_AVX512BITALG = 1 << 12 + cpuid_FMA = 1 << 12 + cpuid_AVX512VPOPCNTDQ = 1 << 14 + cpuid_SSE41 = 1 << 19 + cpuid_SSE42 = 1 << 20 + cpuid_POPCNT = 1 << 23 + cpuid_AES = 1 << 25 + cpuid_OSXSAVE = 1 << 27 + cpuid_AVX = 1 << 28 + + // "Extended Feature Flag" bits returned in EBX for CPUID EAX=0x7 ECX=0x0 + cpuid_BMI1 = 1 << 3 + cpuid_AVX2 = 1 << 5 + cpuid_BMI2 = 1 << 8 + cpuid_ERMS = 1 << 9 + cpuid_AVX512F = 1 << 16 + cpuid_AVX512DQ = 1 << 17 + cpuid_ADX = 1 << 19 + cpuid_AVX512CD = 1 << 28 + cpuid_SHA = 1 << 29 + cpuid_AVX512BW = 1 << 30 + cpuid_AVX512VL = 1 << 31 + + // "Extended Feature Flag" bits returned in ECX for CPUID EAX=0x7 ECX=0x0 + cpuid_AVX512_VBMI = 1 << 1 + cpuid_AVX512_VBMI2 = 1 << 6 + cpuid_GFNI = 1 << 8 + cpuid_AVX512VPCLMULQDQ = 1 << 10 + cpuid_AVX512_BITALG = 1 << 12 + + // edx bits + cpuid_FSRM = 1 << 4 + // edx bits for CPUID 0x80000001 + cpuid_RDTSCP = 1 << 27 + ) + // Additional constants not in internal/cpu + const ( + // eax=1: edx + cpuid_SSE2 = 1 << 26 + // eax=1: ecx + cpuid_CX16 = 1 << 13 + cpuid_RDRAND = 1 << 30 + // eax=7,ecx=0: ebx + cpuid_RDSEED = 1 << 18 + cpuid_AVX512IFMA = 1 << 21 + cpuid_AVX512PF = 1 << 26 + cpuid_AVX512ER = 1 << 27 + // eax=7,ecx=0: edx + cpuid_AVX5124VNNIW = 1 << 2 + cpuid_AVX5124FMAPS = 1 << 3 + cpuid_AMXBF16 = 1 << 22 + cpuid_AMXTile = 1 << 24 + cpuid_AMXInt8 = 1 << 25 + // eax=7,ecx=1: eax + cpuid_AVX512BF16 = 1 << 5 + cpuid_AVXIFMA = 1 << 23 + // eax=7,ecx=1: edx + cpuid_AVXVNNIInt8 = 1 << 4 + ) + Initialized = true maxID, _, _, _ := cpuid(0, 0) @@ -73,90 +147,90 @@ } _, _, ecx1, edx1 := cpuid(1, 0) - X86.HasSSE2 = isSet(26, edx1) + X86.HasSSE2 = isSet(edx1, cpuid_SSE2) - X86.HasSSE3 = isSet(0, ecx1) - X86.HasPCLMULQDQ = isSet(1, ecx1) - X86.HasSSSE3 = isSet(9, ecx1) - X86.HasFMA = isSet(12, ecx1) - X86.HasCX16 = isSet(13, ecx1) - X86.HasSSE41 = isSet(19, ecx1) - X86.HasSSE42 = isSet(20, ecx1) - X86.HasPOPCNT = isSet(23, ecx1) - X86.HasAES = isSet(25, ecx1) - X86.HasOSXSAVE = isSet(27, ecx1) - X86.HasRDRAND = isSet(30, ecx1) + X86.HasSSE3 = isSet(ecx1, cpuid_SSE3) + X86.HasPCLMULQDQ = isSet(ecx1, cpuid_PCLMULQDQ) + X86.HasSSSE3 = isSet(ecx1, cpuid_SSSE3) + X86.HasFMA = isSet(ecx1, cpuid_FMA) + X86.HasCX16 = isSet(ecx1, cpuid_CX16) + X86.HasSSE41 = isSet(ecx1, cpuid_SSE41) + X86.HasSSE42 = isSet(ecx1, cpuid_SSE42) + X86.HasPOPCNT = isSet(ecx1, cpuid_POPCNT) + X86.HasAES = isSet(ecx1, cpuid_AES) + X86.HasOSXSAVE = isSet(ecx1, cpuid_OSXSAVE) + X86.HasRDRAND = isSet(ecx1, cpuid_RDRAND) var osSupportsAVX, osSupportsAVX512 bool // For XGETBV, OSXSAVE bit is required and sufficient. if X86.HasOSXSAVE { eax, _ := xgetbv() // Check if XMM and YMM registers have OS support. - osSupportsAVX = isSet(1, eax) && isSet(2, eax) + osSupportsAVX = isSet(eax, 1<<1) && isSet(eax, 1<<2) if runtime.GOOS == "darwin" { // Darwin requires special AVX512 checks, see cpu_darwin_x86.go osSupportsAVX512 = osSupportsAVX && darwinSupportsAVX512() } else { // Check if OPMASK and ZMM registers have OS support. - osSupportsAVX512 = osSupportsAVX && isSet(5, eax) && isSet(6, eax) && isSet(7, eax) + osSupportsAVX512 = osSupportsAVX && isSet(eax, 1<<5) && isSet(eax, 1<<6) && isSet(eax, 1<<7) } } - X86.HasAVX = isSet(28, ecx1) && osSupportsAVX + X86.HasAVX = isSet(ecx1, cpuid_AVX) && osSupportsAVX if maxID < 7 { return } eax7, ebx7, ecx7, edx7 := cpuid(7, 0) - X86.HasBMI1 = isSet(3, ebx7) - X86.HasAVX2 = isSet(5, ebx7) && osSupportsAVX - X86.HasBMI2 = isSet(8, ebx7) - X86.HasERMS = isSet(9, ebx7) - X86.HasRDSEED = isSet(18, ebx7) - X86.HasADX = isSet(19, ebx7) + X86.HasBMI1 = isSet(ebx7, cpuid_BMI1) + X86.HasAVX2 = isSet(ebx7, cpuid_AVX2) && osSupportsAVX + X86.HasBMI2 = isSet(ebx7, cpuid_BMI2) + X86.HasERMS = isSet(ebx7, cpuid_ERMS) + X86.HasRDSEED = isSet(ebx7, cpuid_RDSEED) + X86.HasADX = isSet(ebx7, cpuid_ADX) - X86.HasAVX512 = isSet(16, ebx7) && osSupportsAVX512 // Because avx-512 foundation is the core required extension + X86.HasAVX512 = isSet(ebx7, cpuid_AVX512F) && osSupportsAVX512 // Because avx-512 foundation is the core required extension if X86.HasAVX512 { X86.HasAVX512F = true - X86.HasAVX512CD = isSet(28, ebx7) - X86.HasAVX512ER = isSet(27, ebx7) - X86.HasAVX512PF = isSet(26, ebx7) - X86.HasAVX512VL = isSet(31, ebx7) - X86.HasAVX512BW = isSet(30, ebx7) - X86.HasAVX512DQ = isSet(17, ebx7) - X86.HasAVX512IFMA = isSet(21, ebx7) - X86.HasAVX512VBMI = isSet(1, ecx7) - X86.HasAVX5124VNNIW = isSet(2, edx7) - X86.HasAVX5124FMAPS = isSet(3, edx7) - X86.HasAVX512VPOPCNTDQ = isSet(14, ecx7) - X86.HasAVX512VPCLMULQDQ = isSet(10, ecx7) - X86.HasAVX512VNNI = isSet(11, ecx7) - X86.HasAVX512GFNI = isSet(8, ecx7) - X86.HasAVX512VAES = isSet(9, ecx7) - X86.HasAVX512VBMI2 = isSet(6, ecx7) - X86.HasAVX512BITALG = isSet(12, ecx7) + X86.HasAVX512CD = isSet(ebx7, cpuid_AVX512CD) + X86.HasAVX512ER = isSet(ebx7, cpuid_AVX512ER) + X86.HasAVX512PF = isSet(ebx7, cpuid_AVX512PF) + X86.HasAVX512VL = isSet(ebx7, cpuid_AVX512VL) + X86.HasAVX512BW = isSet(ebx7, cpuid_AVX512BW) + X86.HasAVX512DQ = isSet(ebx7, cpuid_AVX512DQ) + X86.HasAVX512IFMA = isSet(ebx7, cpuid_AVX512IFMA) + X86.HasAVX512VBMI = isSet(ecx7, cpuid_AVX512_VBMI) + X86.HasAVX5124VNNIW = isSet(edx7, cpuid_AVX5124VNNIW) + X86.HasAVX5124FMAPS = isSet(edx7, cpuid_AVX5124FMAPS) + X86.HasAVX512VPOPCNTDQ = isSet(ecx7, cpuid_AVX512VPOPCNTDQ) + X86.HasAVX512VPCLMULQDQ = isSet(ecx7, cpuid_AVX512VPCLMULQDQ) + X86.HasAVX512VNNI = isSet(ecx7, cpuid_AVX512VNNI) + X86.HasAVX512GFNI = isSet(ecx7, cpuid_AVX512GFNI) + X86.HasAVX512VAES = isSet(ecx7, cpuid_AVX512VAES) + X86.HasAVX512VBMI2 = isSet(ecx7, cpuid_AVX512VBMI2) + X86.HasAVX512BITALG = isSet(ecx7, cpuid_AVX512BITALG) } - X86.HasAMXTile = isSet(24, edx7) - X86.HasAMXInt8 = isSet(25, edx7) - X86.HasAMXBF16 = isSet(22, edx7) + X86.HasAMXTile = isSet(edx7, cpuid_AMXTile) + X86.HasAMXInt8 = isSet(edx7, cpuid_AMXInt8) + X86.HasAMXBF16 = isSet(edx7, cpuid_AMXBF16) // These features depend on the second level of extended features. if eax7 >= 1 { eax71, _, _, edx71 := cpuid(7, 1) if X86.HasAVX512 { - X86.HasAVX512BF16 = isSet(5, eax71) + X86.HasAVX512BF16 = isSet(eax71, cpuid_AVX512BF16) } if X86.HasAVX { - X86.HasAVXIFMA = isSet(23, eax71) - X86.HasAVXVNNI = isSet(4, eax71) - X86.HasAVXVNNIInt8 = isSet(4, edx71) + X86.HasAVXIFMA = isSet(eax71, cpuid_AVXIFMA) + X86.HasAVXVNNI = isSet(eax71, cpuid_AVXVNNI) + X86.HasAVXVNNIInt8 = isSet(edx71, cpuid_AVXVNNIInt8) } } } -func isSet(bitpos uint, value uint32) bool { - return value&(1<<bitpos) != 0 +func isSet(hwc uint32, value uint32) bool { + return hwc&value != 0 } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/golang.org/x/term/terminal.go new/vendor/golang.org/x/term/terminal.go --- old/vendor/golang.org/x/term/terminal.go 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/golang.org/x/term/terminal.go 2026-02-08 16:01:57.000000000 +0100 @@ -160,7 +160,9 @@ keyEnd keyDeleteWord keyDeleteLine + keyDelete keyClearScreen + keyTranspose keyPasteStart keyPasteEnd ) @@ -194,6 +196,8 @@ return keyDeleteLine, b[1:] case 12: // ^L return keyClearScreen, b[1:] + case 20: // ^T + return keyTranspose, b[1:] case 23: // ^W return keyDeleteWord, b[1:] case 14: // ^N @@ -228,6 +232,10 @@ } } + if !pasteActive && len(b) >= 4 && b[0] == keyEscape && b[1] == '[' && b[2] == '3' && b[3] == '~' { + return keyDelete, b[4:] + } + if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { switch b[5] { case 'C': @@ -590,7 +598,7 @@ } t.line = t.line[:t.pos] t.moveCursorToPos(t.pos) - case keyCtrlD: + case keyCtrlD, keyDelete: // Erase the character under the current position. // The EOF case when the line is empty is handled in // readLine(). @@ -600,6 +608,24 @@ } case keyCtrlU: t.eraseNPreviousChars(t.pos) + case keyTranspose: + // This transposes the two characters around the cursor and advances the cursor. Best-effort. + if len(t.line) < 2 || t.pos < 1 { + return + } + swap := t.pos + if swap == len(t.line) { + swap-- // special: at end of line, swap previous two chars + } + t.line[swap-1], t.line[swap] = t.line[swap], t.line[swap-1] + if t.pos < len(t.line) { + t.pos++ + } + if t.echo { + t.moveCursorToPos(swap - 1) + t.writeLine(t.line[swap-1:]) + t.moveCursorToPos(t.pos) + } case keyClearScreen: // Erases the screen and moves the cursor to the home position. t.queue([]rune("\x1b[2J\x1b[H")) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/vendor/modules.txt new/vendor/modules.txt --- old/vendor/modules.txt 2026-01-04 13:48:41.000000000 +0100 +++ new/vendor/modules.txt 2026-02-08 16:01:57.000000000 +0100 @@ -88,7 +88,7 @@ # github.com/gdamore/encoding v1.0.1 ## explicit; go 1.9 github.com/gdamore/encoding -# github.com/gdamore/tcell/v2 v2.13.5 +# github.com/gdamore/tcell/v2 v2.13.8 ## explicit; go 1.24.0 github.com/gdamore/tcell/v2 github.com/gdamore/tcell/v2/terminfo @@ -211,7 +211,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/noder github.com/jesseduffield/go-git/v5/utils/sync github.com/jesseduffield/go-git/v5/utils/trace -# github.com/jesseduffield/gocui v0.3.1-0.20260103133810-b7e030324985 +# github.com/jesseduffield/gocui v0.3.1-0.20260128194906-9d8c3cdfac18 ## explicit; go 1.25 github.com/jesseduffield/gocui # github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 @@ -345,17 +345,17 @@ # golang.org/x/sync v0.19.0 ## explicit; go 1.24.0 golang.org/x/sync/errgroup -# golang.org/x/sys v0.39.0 +# golang.org/x/sys v0.40.0 ## explicit; go 1.24.0 golang.org/x/sys/cpu golang.org/x/sys/execabs golang.org/x/sys/plan9 golang.org/x/sys/unix golang.org/x/sys/windows -# golang.org/x/term v0.38.0 +# golang.org/x/term v0.39.0 ## explicit; go 1.24.0 golang.org/x/term -# golang.org/x/text v0.32.0 +# golang.org/x/text v0.33.0 ## explicit; go 1.24.0 golang.org/x/text/encoding golang.org/x/text/encoding/internal/identifier
