mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-06 05:57:38 -04:00
Give views a transient line flash
Actions that hand control to another application need visible acknowledgement without moving a view's cursor or replacing renderer colors. Reverse the narrow selection bar independently of selection state, and clear transient flashes whenever the terminal UI suspends.
This commit is contained in:
@@ -81,6 +81,23 @@ func TestFlushContentOnly_WritesCorrectContent(t *testing.T) {
|
||||
assert.Equal(t, "Fetching |", status.Buffer())
|
||||
}
|
||||
|
||||
func TestForceFlushViewsContentOnlyDrawsLineFlash(t *testing.T) {
|
||||
g := newTestGui(t)
|
||||
_, main := setupViews(t, g)
|
||||
main.Highlight = true
|
||||
main.SelBgColor = ColorBlue
|
||||
main.SelectedLineColorWidth = 2
|
||||
main.FocusPoint(0, 0, false)
|
||||
|
||||
main.SetLineFlash(0)
|
||||
g.ForceFlushViewsContentOnly(g.Views())
|
||||
|
||||
for x := main.x0 + 1; x <= main.x0+2; x++ {
|
||||
_, style, _ := Screen.Get(x, main.y0+1)
|
||||
assert.True(t, style.HasReverse(), "selection-bar cell at x=%d should flash", x)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessEvent_ContentOnlyEvent_SkipsTaintedCheck(t *testing.T) {
|
||||
g := newTestGui(t)
|
||||
status, main := setupViews(t, g)
|
||||
|
||||
@@ -2149,6 +2149,9 @@ func (g *Gui) Suspend() error {
|
||||
return errors.New("Already suspended")
|
||||
}
|
||||
|
||||
for _, view := range g.views {
|
||||
view.ClearLineFlash()
|
||||
}
|
||||
g.suspended = true
|
||||
|
||||
if err := g.screen.Suspend(); err != nil {
|
||||
|
||||
@@ -67,3 +67,14 @@ func TestResumeSchedulesRedraw(t *testing.T) {
|
||||
assert.Equal(t, eventResize, ev.Type,
|
||||
"resuming must schedule a redraw; without one the screen stays blank until the next event arrives")
|
||||
}
|
||||
|
||||
func TestSuspendClearsLineFlashes(t *testing.T) {
|
||||
g := newTestGui(t)
|
||||
v, err := g.SetView("main", 0, 0, 20, 10, 0)
|
||||
assert.ErrorIs(t, err, ErrUnknownView)
|
||||
v.SetLineFlash(3)
|
||||
|
||||
assert.NoError(t, g.Suspend())
|
||||
assert.Equal(t, -1, v.lineFlashY)
|
||||
assert.NoError(t, g.Resume())
|
||||
}
|
||||
|
||||
@@ -80,6 +80,10 @@ type View struct {
|
||||
// a user starts a range select and then moves the cursor up.
|
||||
rangeSelectStartY int
|
||||
|
||||
// The view line whose selection-width bar is temporarily reversed. A value
|
||||
// of -1 means that no line is flashing.
|
||||
lineFlashY int
|
||||
|
||||
// readBuffer is used for storing unread bytes
|
||||
readBuffer []byte
|
||||
|
||||
@@ -811,6 +815,7 @@ func NewView(name string, x0, y0, x1, y1 int, mode OutputMode) *View {
|
||||
searcher: &searcher{},
|
||||
TextArea: &TextArea{},
|
||||
rangeSelectStartY: -1,
|
||||
lineFlashY: -1,
|
||||
TabWidth: 4,
|
||||
}
|
||||
|
||||
@@ -970,6 +975,10 @@ func (v *View) setCharacter(x, y int, ch string, fgColor, bgColor Attribute, isW
|
||||
fgColor |= AttrUnderline
|
||||
}
|
||||
|
||||
if v.lineFlashY == v.oy+y && (v.SelectedLineColorWidth == 0 || x < v.SelectedLineColorWidth) {
|
||||
fgColor ^= AttrReverse
|
||||
}
|
||||
|
||||
// Don't display empty characters
|
||||
if ch == "" {
|
||||
ch = " "
|
||||
@@ -2323,6 +2332,24 @@ func applySelTextColor(fgColor, selTextColor Attribute) Attribute {
|
||||
return fgColor | selTextColor&AttrStyleBits
|
||||
}
|
||||
|
||||
// SetLineFlash temporarily marks a view line without moving or changing the
|
||||
// selection. The caller owns the lifetime and clears it with ClearLineFlash.
|
||||
func (v *View) SetLineFlash(viewLine int) {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.lineFlashY = viewLine
|
||||
v.needsRedraw = true
|
||||
}
|
||||
|
||||
func (v *View) ClearLineFlash() {
|
||||
v.writeMutex.Lock()
|
||||
defer v.writeMutex.Unlock()
|
||||
|
||||
v.lineFlashY = -1
|
||||
v.needsRedraw = true
|
||||
}
|
||||
|
||||
func lineWrap(line []cell, columns int) [][]cell {
|
||||
if columns == 0 {
|
||||
return [][]cell{line}
|
||||
|
||||
@@ -1113,6 +1113,32 @@ func TestSelectedLinesOfWrappedContent(t *testing.T) {
|
||||
assert.Equal(t, []string{"a line that wraps"}, v.SelectedLines())
|
||||
}
|
||||
|
||||
func TestLineFlashReversesTheSelectionBarWithoutChangingSelection(t *testing.T) {
|
||||
WithSimulationScreen(t, 14, 6)
|
||||
|
||||
v := NewView("name", 0, 0, 11, 5, OutputNormal)
|
||||
v.Highlight = true
|
||||
v.SelBgColor = ColorBlue
|
||||
v.SelectedLineColorWidth = 2
|
||||
v.writeString("one\ntwo\nthree\n")
|
||||
v.FocusPoint(0, 1, false)
|
||||
v.SetLineFlash(1)
|
||||
v.draw(true)
|
||||
|
||||
for x := 1; x <= 2; x++ {
|
||||
_, style, _ := Screen.Get(x, 2)
|
||||
assert.True(t, style.HasReverse(), "selection-bar cell at (%d, 2) should flash", x)
|
||||
}
|
||||
_, style, _ := Screen.Get(3, 2)
|
||||
assert.False(t, style.HasReverse(), "the flash should stop after the selection bar")
|
||||
assert.Equal(t, "two", v.SelectedLine(), "flashing should not change the selection")
|
||||
|
||||
v.ClearLineFlash()
|
||||
v.draw(true)
|
||||
_, style, _ = Screen.Get(1, 2)
|
||||
assert.False(t, style.HasReverse(), "clearing should remove the flash")
|
||||
}
|
||||
|
||||
// Resizing a view throws away the wrapping of its content and wraps it again for
|
||||
// the new width, which moves every line of it to a different view line. The
|
||||
// positions into the view count view lines, so they all have to come along.
|
||||
|
||||
Reference in New Issue
Block a user