Show the diff view's narrow selection bar on the right edge too

This is an improvement for users who use a side-by-side diff renderer on
a very large screen; in this scenario it was hard to see which hunk on
the right side was selected.
This commit is contained in:
Stefan Haller committed 2026-10-10 14:20:04 +02:00
1 parent a5bf29e092
commit 96f93ec79d
2 files changed
+37 -9

No files matched your search

+13 -9
View File
@@ -174,9 +174,11 @@ type View struct {
HighlightInactive bool
// If SelectedLineColorWidth is greater than zero, a highlighted line is painted
// in the selection colors on that many columns at its left edge only, rather
// than across its whole width, leaving the line's own colors to show through.
// For content that conveys meaning by color of its own.
// in the selection colors on that many columns at its left and right edges only,
// rather than across its whole width, leaving the line's own colors to show
// through. For content that conveys meaning by color of its own. A double-width
// character that reaches into these columns is painted whole, so the colored part
// can be a column wider.
SelectedLineColorWidth int
// InclusionGutterMarker is the glyph the inclusion gutter draws on a marked line
@@ -936,8 +938,8 @@ func (v *View) InclusionGutterWidthWhenShown() int {
// setCharacter sets a character (grapheme cluster) at the given point relative to the view. It applies
// the specified colors, taking into account if the cell must be highlighted. Also, it checks if the
// position is valid.
func (v *View) setCharacter(x, y int, ch string, fgColor, bgColor Attribute, isWindowFocused bool) {
// position is valid. width is the number of columns the character takes up.
func (v *View) setCharacter(x, y int, ch string, width int, fgColor, bgColor Attribute, isWindowFocused bool) {
maxX, maxY := v.Size()
if x < 0 || x >= maxX || y < 0 || y >= maxY {
return
@@ -957,7 +959,8 @@ func (v *View) setCharacter(x, y int, ch string, fgColor, bgColor Attribute, isW
}
colorWidth := v.SelectedLineColorWidth
if y >= rangeSelectStart && y <= rangeSelectEnd && (colorWidth == 0 || x < colorWidth) {
if y >= rangeSelectStart && y <= rangeSelectEnd &&
(colorWidth == 0 || x < colorWidth || x+width > v.InnerWidth()-colorWidth) {
fgColor = applySelTextColor(fgColor, v.SelTextColor)
if v.HighlightInactive || !isWindowFocused {
bgColor = (bgColor & AttrStyleBits) | v.InactiveViewSelBgColor
@@ -1869,10 +1872,11 @@ func (v *View) draw(isWindowFocused bool) {
// content begins after it. The blanks go through setCharacter like everything
// else, so that a selection reaching the left edge covers the gutter too.
for gx := range gutterWidth {
v.setCharacter(gx, y, " ", v.FgColor, v.BgColor, isWindowFocused)
v.setCharacter(gx, y, " ", 1, v.FgColor, v.BgColor, isWindowFocused)
}
if gutterWidth > 0 && vline.linesY < len(v.inclusionGutterMarks) && v.inclusionGutterMarks[vline.linesY] {
v.setCharacter(0, y, v.InclusionGutterMarker, v.InclusionGutterMarkerColor, v.BgColor, isWindowFocused)
v.setCharacter(0, y, v.InclusionGutterMarker, uniseg.StringWidth(v.InclusionGutterMarker),
v.InclusionGutterMarkerColor, v.BgColor, isWindowFocused)
}
// x tracks the current x position in the view, and cellIdx tracks the
@@ -1925,7 +1929,7 @@ func (v *View) draw(isWindowFocused bool) {
fgColor |= AttrUnderline
}
v.setCharacter(x, y, c.chr, fgColor, bgColor, isWindowFocused)
v.setCharacter(x, y, c.chr, c.width, fgColor, bgColor, isWindowFocused)
x += c.width
cellIdx++
+24
View File
@@ -1139,6 +1139,30 @@ func TestLineFlashReversesTheSelectionBarWithoutChangingSelection(t *testing.T)
assert.False(t, style.HasReverse(), "clearing should remove the flash")
}
// The selection bar on the right edge covers a double-width character that reaches
// into it, like the bar on the left edge does.
func TestSelectionBarOnTheRightEdgeCoversADoubleWidthCharacter(t *testing.T) {
WithSimulationScreen(t, 14, 6)
v := NewView("name", 0, 0, 11, 5, OutputNormal) // InnerWidth 10
v.Highlight = true
v.SelBgColor = ColorBlue
v.SelectedLineColorWidth = 2
// に takes view columns 7 and 8, and the bar the last two, 8 and 9.
v.writeString("abcdefgにj\n")
v.draw(true)
// The frame puts view column x at screen column x+1.
highlighted := func(x int) bool {
_, style, _ := Screen.Get(x+1, 1)
return style.GetBackground() != tcell.ColorDefault
}
assert.False(t, highlighted(6))
assert.True(t, highlighted(7), "に reaches into the bar")
assert.True(t, highlighted(9))
}
// 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.