diff --git a/pkg/gocui/view.go b/pkg/gocui/view.go index b195768dc..ec20ab429 100644 --- a/pkg/gocui/view.go +++ b/pkg/gocui/view.go @@ -1878,13 +1878,7 @@ func (v *View) draw(isWindowFocused bool) { var c cell for x < maxX { - if x < 0 { - if cellIdx < len(vline.line) { - x += uniseg.StringWidth(vline.line[cellIdx].chr) - cellIdx++ - continue - } - + if x < 0 && cellIdx >= len(vline.line) { // no more characters to write so we're only going to be printing empty cells // past this point x = gutterWidth @@ -1897,6 +1891,22 @@ func (v *View) draw(isWindowFocused bool) { c = vline.line[cellIdx] } + // Skip the cells scrolled out to the left. + if x < 0 && x+c.width <= 0 { + x += c.width + cellIdx++ + continue + } + + // A terminal can't show half of a double-width character, so one that is cut + // off by the left or right edge shows as a blank in the column of it that is + // on screen. + if x < 0 || x+c.width > maxX { + c.chr = " " + c.width = 1 + x = max(x, 0) + } + fgColor := c.fgColor if fgColor == ColorDefault { fgColor = v.FgColor diff --git a/pkg/gocui/view_test.go b/pkg/gocui/view_test.go index 5731d853e..d5eed55ac 100644 --- a/pkg/gocui/view_test.go +++ b/pkg/gocui/view_test.go @@ -1166,6 +1166,47 @@ func TestResizingAWrappingViewKeepsItsPlaceInTheContent(t *testing.T) { assert.Equal(t, []string{"another wrapping line"}, v.SelectedLines()) } +// A double-width character that starts in the last column doesn't fit, and shows as a +// blank there rather than reaching into the frame. +func TestDoubleWidthCharacterAtTheRightEdge(t *testing.T) { + WithSimulationScreen(t, 14, 6) + + v := NewView("name", 0, 0, 11, 5, OutputNormal) // InnerWidth 10 + + v.writeString("abcdefghiプ\n") + v.draw(true) + + // The frame puts view column 9 at screen column 10. + chr, _, width := Screen.Get(10, 1) + assert.Equal(t, " ", chr) + assert.Equal(t, 1, width) +} + +// A double-width character that is scrolled halfway out to the left shows as a blank +// in the column it still reaches into, so that the column is drawn like every other. +func TestDoubleWidthCharacterCutOffAtTheLeftEdge(t *testing.T) { + WithSimulationScreen(t, 14, 6) + + v := NewView("name", 0, 0, 11, 5, OutputNormal) // InnerWidth 10 + v.Highlight = true + v.SelBgColor = ColorBlue + + v.writeString("にプ\n") + v.SetOriginX(1) + v.draw(true) + + // The frame puts view column 0 at screen column 1. + chr, style, _ := Screen.Get(1, 1) + assert.Equal(t, " ", chr) + assert.Equal(t, + true, + style.GetBackground() != tcell.ColorDefault, + "the selection covers the column") + + chr, _, _ = Screen.Get(2, 1) + assert.Equal(t, "プ", chr) +} + // The inclusion gutter reserves columns at the left of every line, draws its marker // on the marked lines only, and moves the content out of the way. func TestInclusionGutter(t *testing.T) {