Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion internal/display/bufwindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,18 @@ func (w *BufWindow) displayBuffer() {
bline := b.LineBytes(bloc.Y)
blineLen := util.CharacterCount(bline)

// An exact-width line has no extra row for a cursor at its end,
// so keep the wrapped row while a cursor is there.
cursorAtEOL := false
if w.active && softwrap {
for _, c := range cursors {
if !c.HasSelection() && c.Y == bloc.Y && c.X == blineLen {
cursorAtEOL = true
break
}
}
}

leadingwsEnd := len(util.GetLeadingWhitespace(bline))
trailingwsStart := blineLen - util.CharacterCount(util.GetTrailingWhitespace(bline))

Expand Down Expand Up @@ -791,7 +803,7 @@ func (w *BufWindow) displayBuffer() {
wordwidth = 0

// If we reach the end of the window then we either stop or we wrap for softwrap
if vloc.X >= maxWidth {
if vloc.X >= maxWidth && (len(line) > 0 || cursorAtEOL) {
if !softwrap {
break
} else {
Expand Down
104 changes: 104 additions & 0 deletions internal/display/bufwindow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package display

import (
"strings"
"testing"

"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/micro-editor/micro/v2/internal/config"
ulua "github.com/micro-editor/micro/v2/internal/lua"
"github.com/micro-editor/micro/v2/internal/screen"
"github.com/micro-editor/tcell/v2"
"github.com/stretchr/testify/assert"
lua "github.com/yuin/gopher-lua"
)

func newSoftwrapTestWindow(t *testing.T, text string, width, height int) *BufWindow {
t.Helper()

// NewBufferFromString needs a Lua state to expose the buffer to plugins.
if ulua.L == nil {
ulua.L = lua.NewState()
}

if err := config.InitGlobalSettings(); err != nil {
t.Fatalf("InitGlobalSettings: %v", err)
}
// Use the real terminal cursor so SimulationScreen.GetCursor() reports it;
// fakecursor defaults to true on some Windows consoles.
config.GlobalSettings["fakecursor"] = false
if _, err := screen.InitSimScreen(); err != nil {
t.Fatalf("InitSimScreen: %v", err)
}

buf := buffer.NewBufferFromString(text, "", buffer.BTDefault)
buf.Settings["softwrap"] = true
// Turn off the ruler so gutterOffset is 0 and content width == width.
buf.Settings["ruler"] = false

w := NewBufWindow(0, 0, width, height, buf)
w.Resize(width, height)
return w
}

// A line that exactly fills the window must not push the next line down by a
// blank wrapped row.
func TestDisplayBufferNoExtraRowAtExactWidth(t *testing.T) {
tests := []struct {
name string
width int
expectedRows int
}{
{"one narrower than width: no wrap", 9, 1},
{"exactly window width: must not wrap to an extra row", 10, 1},
{"one wider than width: wraps with 1 overflow char", 11, 2},
{"exactly two window widths: wraps once, no extra row", 20, 2},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
w := newSoftwrapTestWindow(t, strings.Repeat("x", tc.width)+"\nSECOND", 10, 5)
w.displayBuffer()

r, _, _, _ := screen.Screen.GetContent(0, tc.expectedRows)
assert.Equal(t, 'S', r, "second buffer line should render right after the first line's %d row(s), with no extra blank rows between them", tc.expectedRows)
})
}
}

// A cursor at the end of a line that exactly fills the window must still be
// drawn on screen, not past the right edge.
func TestDisplayBufferCursorVisibleAtExactWidthEOL(t *testing.T) {
tests := []struct {
name string
width int
}{
{"one narrower than width", 9},
{"exactly window width", 10},
{"one wider than width", 11},
{"exactly two window widths", 20},
}

const winWidth = 10

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
w := newSoftwrapTestWindow(t, strings.Repeat("x", tc.width)+"\nSECOND", winWidth, 5)
w.active = true

c := w.Buf.GetActiveCursor()
c.GotoLoc(buffer.Loc{X: tc.width, Y: 0})

w.displayBuffer()

sim := screen.Screen.(tcell.SimulationScreen)
cx, cy, vis := sim.GetCursor()

assert.True(t, vis, "cursor should be visible")
assert.GreaterOrEqual(t, cx, 0)
assert.Less(t, cx, winWidth, "cursor x must be within the window, not off the right edge")
assert.GreaterOrEqual(t, cy, 0)
assert.Less(t, cy, 5, "cursor y must be within the window")
})
}
}
6 changes: 4 additions & 2 deletions internal/display/softwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (w *BufWindow) getVLocFromLoc(loc buffer.Loc) VLoc {
wordwidth = 0
wordoffset = 0

if vloc.VisualX >= w.bufWidth {
if vloc.VisualX >= w.bufWidth && len(line) > 0 {
vloc.Row++
vloc.VisualX = 0
}
Expand Down Expand Up @@ -215,7 +215,9 @@ func (w *BufWindow) getLocFromVLoc(svloc VLoc) buffer.Loc {
widths = widths[:0]
wordwidth = 0

if vloc.VisualX >= w.bufWidth {
// len(line) > 0 is a no-op here (loc is returned, not vloc), kept in
// sync with getVLocFromLoc in case vloc is ever read past the loop.
if vloc.VisualX >= w.bufWidth && len(line) > 0 {
vloc.Row++
vloc.VisualX = 0
}
Expand Down
38 changes: 38 additions & 0 deletions internal/display/softwrap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package display

import (
"strings"
"testing"

"github.com/micro-editor/micro/v2/internal/buffer"
"github.com/stretchr/testify/assert"
)

// A line that exactly fills the window's content width must not count as
// needing an extra, empty row.
func TestGetRowCountExactWidth(t *testing.T) {
tests := []struct {
name string
width int
expectedRows int
expectedLastRowX int
}{
{"one narrower than width: no wrap", 79, 1, 79},
{"exactly window width: must not wrap to an extra row", 80, 1, 80},
{"one wider than width: wraps with 1 overflow char", 81, 2, 1},
{"two wider than width: wraps with 2 overflow chars", 82, 2, 2},
{"exactly two window widths: wraps once, no extra row", 160, 2, 80},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
w := newSoftwrapTestWindow(t, strings.Repeat("x", tc.width), 80, 24)

assert.Equal(t, tc.expectedRows, w.getRowCount(0))

vloc := w.getVLocFromLoc(buffer.Loc{X: tc.width, Y: 0})
assert.Equal(t, tc.expectedRows-1, vloc.Row)
assert.Equal(t, tc.expectedLastRowX, vloc.VisualX)
})
}
}