From 189e622658643d98138a2f62f3c143b8973b94c1 Mon Sep 17 00:00:00 2001 From: Johan Walles Date: Fri, 20 Sep 2024 08:04:56 +0200 Subject: [PATCH] Simplify last-cell-on-a-row-is-wide handling By doing the clipping in SetCell(). This is less intrusive than doing it in renderLine(). --- twin/fake-screen.go | 23 +++++++++++++-------- twin/screen.go | 49 +++++++++++++++++++++++---------------------- twin/screen_test.go | 20 +++++++++--------- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/twin/fake-screen.go b/twin/fake-screen.go index bf455a9..e60c4a5 100644 --- a/twin/fake-screen.go +++ b/twin/fake-screen.go @@ -40,26 +40,33 @@ func (screen *FakeScreen) Clear() { } } -func (screen *FakeScreen) SetCell(column int, row int, cell StyledRune) int { +func (screen *FakeScreen) SetCell(column int, row int, styledRune StyledRune) int { // This method's contents has been copied from UnixScreen.Clear() if column < 0 { - return cell.Width() + return styledRune.Width() } if row < 0 { - return cell.Width() + return styledRune.Width() } width, height := screen.Size() if column >= width { - return cell.Width() + return styledRune.Width() } if row >= height { - return cell.Width() + return styledRune.Width() } - screen.cells[row][column] = cell - return cell.Width() + if column+styledRune.Width() > width { + // This cell is too wide for the screen, write a space instead + screen.cells[row][column] = NewStyledRune(' ', styledRune.Style) + return styledRune.Width() + } + + screen.cells[row][column] = styledRune + + return styledRune.Width() } func (screen *FakeScreen) Show() { @@ -88,5 +95,5 @@ func (screen *FakeScreen) Events() chan Event { } func (screen *FakeScreen) GetRow(row int) []StyledRune { - return withoutHiddenRunes(screen.cells[row], screen.width) + return withoutHiddenRunes(screen.cells[row]) } diff --git a/twin/screen.go b/twin/screen.go index c4d1a16..5188ce5 100644 --- a/twin/screen.go +++ b/twin/screen.go @@ -37,7 +37,7 @@ type Screen interface { Clear() // Returns the width of the rune just added, in number of columns - SetCell(column int, row int, cell StyledRune) int + SetCell(column int, row int, styledRune StyledRune) int // Render our contents into the terminal window Show() @@ -623,24 +623,31 @@ func parseTerminalBgColorResponse(responseBytes []byte) *Color { return &color } -func (screen *UnixScreen) SetCell(column int, row int, cell StyledRune) int { +func (screen *UnixScreen) SetCell(column int, row int, styledRune StyledRune) int { if column < 0 { - return cell.Width() + return styledRune.Width() } if row < 0 { - return cell.Width() + return styledRune.Width() } width, height := screen.Size() if column >= width { - return cell.Width() + return styledRune.Width() } if row >= height { - return cell.Width() + return styledRune.Width() } - screen.cells[row][column] = cell - return cell.Width() + if column+styledRune.Width() > width { + // This cell is too wide for the screen, write a space instead + screen.cells[row][column] = NewStyledRune(' ', styledRune.Style) + return styledRune.Width() + } + + screen.cells[row][column] = styledRune + + return styledRune.Width() } func (screen *UnixScreen) Clear() { @@ -656,21 +663,16 @@ func (screen *UnixScreen) Clear() { // A cell is considered hidden if it's preceded by a wide character that spans // multiple columns. -// -// Also, if the last cell on the screen is a wide character that would span the -// screen border, it will be considered hidden as well. -func withoutHiddenRunes(runes []StyledRune, screenWidth int) []StyledRune { +func withoutHiddenRunes(runes []StyledRune) []StyledRune { result := make([]StyledRune, 0, len(runes)) - renderedWidth := 0 - for _, char := range runes { - charWidth := char.Width() - if renderedWidth+charWidth > screenWidth { - // This character would span the screen border - break + + for i := 0; i < len(runes); i++ { + if i > 0 && runes[i-1].Width() == 2 { + // This is a hidden rune + continue } - result = append(result, char) - renderedWidth += charWidth + result = append(result, runes[i]) } return result @@ -678,8 +680,8 @@ func withoutHiddenRunes(runes []StyledRune, screenWidth int) []StyledRune { // Returns the rendered line, plus how many information carrying cells went into // it -func renderLine(row []StyledRune, screenWidth int, terminalColorCount ColorCount) (string, int) { - row = withoutHiddenRunes(row, screenWidth) +func renderLine(row []StyledRune, terminalColorCount ColorCount) (string, int) { + row = withoutHiddenRunes(row) // Strip trailing whitespace lastSignificantCellIndex := len(row) - 1 @@ -746,9 +748,8 @@ func (screen *UnixScreen) showNLines(height int, clearFirst bool) { builder.WriteString("\x1b[1;1H") } - screenWidth, _ := screen.Size() for row := 0; row < height; row++ { - rendered, lineLength := renderLine(screen.cells[row], screenWidth, screen.terminalColorCount) + rendered, lineLength := renderLine(screen.cells[row], screen.terminalColorCount) builder.WriteString(rendered) wasLastLine := row == (height - 1) diff --git a/twin/screen_test.go b/twin/screen_test.go index 61aa395..39f3c83 100644 --- a/twin/screen_test.go +++ b/twin/screen_test.go @@ -65,7 +65,7 @@ func TestRenderLine(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 2) reset := "" reversed := "" @@ -80,7 +80,7 @@ func TestRenderLine(t *testing.T) { func TestRenderLineEmpty(t *testing.T) { row := []StyledRune{} - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 0) // All lines are expected to stand on their own, so we always need to clear @@ -96,7 +96,7 @@ func TestRenderLineLastReversed(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 1) reset := "" reversed := "" @@ -114,7 +114,7 @@ func TestRenderLineLastNonSpace(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 1) reset := "" clearToEol := "" @@ -135,7 +135,7 @@ func TestRenderLineLastReversedPlusTrailingSpace(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 1) reset := "" reversed := "" @@ -157,7 +157,7 @@ func TestRenderLineOnlyTrailingSpaces(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 0) // All lines are expected to stand on their own, so we always need to clear @@ -173,7 +173,7 @@ func TestRenderLineLastReversedSpaces(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 1) reset := "" reversed := "" @@ -190,7 +190,7 @@ func TestRenderLineNonPrintable(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 1) reset := "" white := "" @@ -211,7 +211,7 @@ func TestRenderHyperlinkAtEndOfLine(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 1) assert.Equal(t, @@ -236,7 +236,7 @@ func TestMultiCharHyperlink(t *testing.T) { }, } - rendered, count := renderLine(row, 100, ColorCount16) + rendered, count := renderLine(row, ColorCount16) assert.Equal(t, count, 3) assert.Equal(t,