1
0
mirror of https://github.com/mum4k/termdash.git synced 2025-04-25 13:48:50 +08:00

Polishing the editor code.

Some helper functions and better comments.
This commit is contained in:
Jakub Sobon 2019-04-22 23:46:36 -04:00
parent 22c89bc2a7
commit c2f5326954
No known key found for this signature in database
GPG Key ID: F2451A77FB05D3B7
2 changed files with 59 additions and 20 deletions

View File

@ -124,7 +124,9 @@ func curMaxIdx(start, end, cells, runeCount int) int {
// shiftLeft shifts the visible range left so that it again contains the
// cursor.
func (fd *fieldData) shiftLeft(start, end, cells, curDataPos int) (int, int) {
// The visible range includes all fieldData indexes
// in range start <= idx < end.
func (fd *fieldData) shiftLeft(start, cells, curDataPos int) (int, int) {
var startIdx int
switch {
case curDataPos == 0 || cells < minForArrows:
@ -142,7 +144,9 @@ func (fd *fieldData) shiftLeft(start, end, cells, curDataPos int) (int, int) {
// shiftRight shifts the visible range right so that it again contains the
// cursor.
func (fd *fieldData) shiftRight(start, end, cells, curDataPos int) (int, int) {
// The visible range includes all fieldData indexes
// in range start <= idx < end.
func (fd *fieldData) shiftRight(start, cells, curDataPos int) (int, int) {
var endIdx int
switch dataLen := len(*fd); {
case curDataPos == dataLen:
@ -158,50 +162,82 @@ func (fd *fieldData) shiftRight(start, end, cells, curDataPos int) (int, int) {
forRunes := cells - 1
startIdx := fd.cellsBefore(forRunes, endIdx)
// Invariant, if counting form the back ends in the middle of a full-width
// rune, cellsAfter doesn't include the full-width rune. This means that we
// might have recovered space for one half-with rune at the end if there is
// one.
endIdx = fd.cellsAfter(forRunes, startIdx)
endIdx++ // Space for the cursor.
return startIdx, endIdx
}
// runesIn returns runes that are in the visible range.
// lastVisible given an end index of visible range asserts whether the last
// rune in the data is visible.
// The visible range includes all fieldData indexes
// in range start <= idx < end.
func (fd *fieldData) lastVisible(end int) bool {
return end-1 >= len(*fd)
}
// runesIn returns all the runes in the visible range.
// The visible range includes all fieldData indexes
// in range start <= idx < end.
func (fd *fieldData) runesIn(start, end int) []rune {
var runes []rune
for i, r := range (*fd)[start:] {
if i+start > end-2 { // One last space is for the cursor after the text.
break
}
runes = append(runes, r)
}
return runes
}
// fitRunes starting from the firstRune index returns runes that take at most
// the specified number of cells. The last cell is reserved for a cursor
// position used for appending new runes.
// This might return smaller number of runes than the size of the range,
// depending on the width of the individual runes.
func (fd *fieldData) runesIn(firstRune, curPos, cells int) (string, int) {
func (fd *fieldData) fitRunes(firstRune, curPos, cells int) (string, int) {
forRunes := cells - 1 // One cell reserved for the cursor when appending.
// Determine how many runes fit from the start.
start := firstRune
end := fd.cellsAfter(forRunes, start)
end++
if start > 0 && end-1 >= len(*fd) {
if start > 0 && fd.lastVisible(end) {
// Start is in the middle, end is visible.
// Fit runes from the end.
end = len(*fd)
start = fd.cellsBefore(forRunes, end)
end++ // Space for the cursor within the visible range.
}
// The fitting of runes might have resulted in a visible range that no
// longer contains the cursor (it became shorter) or the cursor was outside
// to begin with (due to cursorLeft() or cursorRight() calls).
// Shift the range so the cursor is again inside.
if curPos < curMinIdx(start, cells) {
start, end = fd.shiftLeft(start, end, cells, curPos)
start, end = fd.shiftLeft(start, cells, curPos)
} else if curPos > curMaxIdx(start, end, cells, len(*fd)) {
start, end = fd.shiftRight(start, end, cells, curPos)
start, end = fd.shiftRight(start, cells, curPos)
}
var runes []rune
for i, r := range (*fd)[start:] {
if i+start > end-2 {
break
}
runes = append(runes, r)
}
//log.Printf("runes: %v", string(runes))
runes := fd.runesIn(start, end)
useArrows := cells >= minForArrows
var b strings.Builder
for i, r := range runes {
switch {
case useArrows && i == 0 && start > 0:
// Indicate that start is hidden by replacing the first visible
// rune with an arrow.
b.WriteRune('⇦')
if rw := runewidth.RuneWidth(r); rw == 2 {
// If the replaced rune was a full-width rune, place two arrows
// to keep the same space allocation as pre-calculated.
b.WriteRune('⇦')
}
@ -210,7 +246,10 @@ func (fd *fieldData) runesIn(firstRune, curPos, cells int) (string, int) {
}
}
if useArrows && end-1 < len(*fd) {
if useArrows && !fd.lastVisible(end) {
// Indicate that end is hidden by placing an arrow at the end.
// THis has no impact on space allocation, since the last cell is
// always reserved for the cursor or the arrow.
b.WriteRune('⇨')
}
return b.String(), start
@ -270,7 +309,7 @@ func (fe *fieldEditor) viewFor(width int) (string, int, error) {
if min := minFieldWidth; width < min { // One for left arrow, two for one full-width rune and one for the cursor.
return "", -1, fmt.Errorf("width %d is too small, the minimum is %d", width, min)
}
runes, start := fe.data.runesIn(fe.firstRune, fe.curDataPos, width)
runes, start := fe.data.fitRunes(fe.firstRune, fe.curDataPos, width)
fe.firstRune = start
return runes, fe.curCell(width), nil
}

View File

@ -1500,14 +1500,14 @@ func TestFieldEditor(t *testing.T) {
gotView, gotCurIdx, err := fe.viewFor(tc.width)
if (err != nil) != tc.wantErr {
t.Errorf("viewFor => unexpected error: %v, wantErr: %v", err, tc.wantErr)
t.Errorf("viewFor(%d) => unexpected error: %v, wantErr: %v", tc.width, err, tc.wantErr)
}
if err != nil {
return
}
if gotView != tc.wantView || gotCurIdx != tc.wantCurIdx {
t.Errorf("viewFor => (%q, %d), want (%q, %d)", gotView, gotCurIdx, tc.wantView, tc.wantCurIdx)
t.Errorf("viewFor(%d) => (%q, %d), want (%q, %d)", tc.width, gotView, gotCurIdx, tc.wantView, tc.wantCurIdx)
}
gotContent := fe.content()