Rewrite the readline redraw code to make the screen lines explicit
This commit is contained in:
parent
f945ef8ee8
commit
3c4a411cad
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.19
|
||||
|
||||
require (
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924
|
||||
github.com/google/go-cmp v0.5.8
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f
|
||||
|
||||
2
go.sum
2
go.sum
@ -1,5 +1,7 @@
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924 h1:DG4UyTVIujioxwJc8Zj8Nabz1L1wTgQ/xNBSQDfdP3I=
|
||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
|
||||
@ -244,6 +244,14 @@ func (self *Loop) EndBracketedPaste() {
|
||||
self.QueueWriteString(BRACKETED_PASTE.EscapeCodeToReset())
|
||||
}
|
||||
|
||||
func (self *Loop) AllowLineWrapping(allow bool) {
|
||||
if allow {
|
||||
self.QueueWriteString(DECAWM.EscapeCodeToSet())
|
||||
} else {
|
||||
self.QueueWriteString(DECAWM.EscapeCodeToReset())
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Loop) Quit(exit_code int) {
|
||||
self.exit_code = exit_code
|
||||
self.keep_going = false
|
||||
|
||||
@ -162,6 +162,33 @@ func (self *Readline) move_cursor_right(amt uint, traverse_line_breaks bool) uin
|
||||
return amt_moved
|
||||
}
|
||||
|
||||
func move_up_one_line(self *Readline) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *Readline) move_cursor_up(amt uint) uint {
|
||||
ans := uint(0)
|
||||
if self.screen_width == 0 {
|
||||
self.update_current_screen_size()
|
||||
}
|
||||
for ans < amt {
|
||||
if move_up_one_line(self) {
|
||||
ans++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func (self *Readline) move_cursor_down(amt uint) uint {
|
||||
ans := uint(0)
|
||||
if self.screen_width == 0 {
|
||||
self.update_current_screen_size()
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func (self *Readline) move_to_start_of_line() bool {
|
||||
if self.cursor.X > 0 {
|
||||
self.cursor.X = 0
|
||||
@ -306,6 +333,31 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error {
|
||||
return self.perform_action(ActionAcceptInput, 1)
|
||||
case ActionAcceptInput:
|
||||
return ErrAcceptInput
|
||||
case ActionCursorUp:
|
||||
if self.move_cursor_up(repeat_count) > 0 {
|
||||
return nil
|
||||
}
|
||||
case ActionCursorDown:
|
||||
if self.move_cursor_down(repeat_count) > 0 {
|
||||
return nil
|
||||
}
|
||||
case ActionHistoryPreviousOrCursorUp:
|
||||
if self.cursor.Y == 0 {
|
||||
r := self.perform_action(ActionHistoryPrevious, repeat_count)
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return self.perform_action(ActionCursorUp, repeat_count)
|
||||
case ActionHistoryNextOrCursorDown:
|
||||
if self.cursor.Y == 0 {
|
||||
r := self.perform_action(ActionHistoryNext, repeat_count)
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return self.perform_action(ActionCursorDown, repeat_count)
|
||||
|
||||
}
|
||||
return ErrCouldNotPerformAction
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import (
|
||||
"fmt"
|
||||
"kitty/tools/tui/loop"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
@ -56,6 +58,33 @@ func TestAddText(t *testing.T) {
|
||||
}, "abcd\nxy12\n34", "z", "abcd\nxy12\n34z")
|
||||
}
|
||||
|
||||
func TestGetScreenLines(t *testing.T) {
|
||||
lp, _ := loop.New()
|
||||
rl := New(lp, RlInit{Prompt: "$$ "})
|
||||
rl.screen_width = 10
|
||||
|
||||
tsl := func(expected ...ScreenLine) {
|
||||
q := rl.get_screen_lines()
|
||||
actual := make([]ScreenLine, len(q))
|
||||
for i, x := range q {
|
||||
actual[i] = *x
|
||||
}
|
||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||
t.Fatalf("Did not get expected screen lines for: %#v\n%s", rl.AllText(), diff)
|
||||
}
|
||||
}
|
||||
tsl(ScreenLine{PromptLen: 3, CursorCell: 3})
|
||||
rl.add_text("123")
|
||||
tsl(ScreenLine{PromptLen: 3, CursorCell: 6, Text: "123", CursorTextPos: 3, TextLengthInCells: 3})
|
||||
rl.add_text("456")
|
||||
tsl(ScreenLine{PromptLen: 3, CursorCell: 9, Text: "123456", CursorTextPos: 6, TextLengthInCells: 6})
|
||||
rl.add_text("7")
|
||||
tsl(
|
||||
ScreenLine{PromptLen: 3, CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7},
|
||||
ScreenLine{OffsetInParentLine: 7},
|
||||
)
|
||||
}
|
||||
|
||||
func TestCursorMovement(t *testing.T) {
|
||||
dt := test_func(t)
|
||||
|
||||
|
||||
@ -47,6 +47,12 @@ const (
|
||||
ActionCursorRight
|
||||
ActionEndInput
|
||||
ActionAcceptInput
|
||||
ActionCursorUp
|
||||
ActionHistoryPreviousOrCursorUp
|
||||
ActionCursorDown
|
||||
ActionHistoryNextOrCursorDown
|
||||
ActionHistoryNext
|
||||
ActionHistoryPrevious
|
||||
)
|
||||
|
||||
type Readline struct {
|
||||
|
||||
@ -9,26 +9,6 @@ import (
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
func (self *Readline) write_line_with_prompt(line, prompt string, screen_width int) int {
|
||||
self.loop.QueueWriteString(prompt)
|
||||
self.loop.QueueWriteString(line)
|
||||
w := wcswidth.Stringwidth(prompt) + wcswidth.Stringwidth(line)
|
||||
num_lines := w / screen_width
|
||||
if w%screen_width == 0 {
|
||||
num_lines--
|
||||
}
|
||||
return num_lines
|
||||
}
|
||||
|
||||
func (self *Readline) move_cursor_to_text_position(pos, screen_width int) int {
|
||||
num_of_lines := pos / screen_width
|
||||
self.loop.MoveCursorVertically(num_of_lines)
|
||||
self.loop.QueueWriteString("\r")
|
||||
x := pos % screen_width
|
||||
self.loop.MoveCursorHorizontally(x)
|
||||
return num_of_lines
|
||||
}
|
||||
|
||||
func (self *Readline) update_current_screen_size() {
|
||||
screen_size, err := self.loop.ScreenSize()
|
||||
if err != nil {
|
||||
@ -44,6 +24,52 @@ func (self *Readline) update_current_screen_size() {
|
||||
self.screen_width = int(screen_size.WidthCells)
|
||||
}
|
||||
|
||||
type ScreenLine struct {
|
||||
ParentLineNumber, OffsetInParentLine, PromptLen int
|
||||
TextLengthInCells, CursorCell, CursorTextPos int
|
||||
Text string
|
||||
}
|
||||
|
||||
func (self *Readline) get_screen_lines() []*ScreenLine {
|
||||
if self.screen_width == 0 {
|
||||
self.update_current_screen_size()
|
||||
}
|
||||
ans := make([]*ScreenLine, 0, len(self.lines))
|
||||
found_cursor := false
|
||||
for i, line := range self.lines {
|
||||
plen := self.prompt_len
|
||||
if i > 0 {
|
||||
plen = self.continuation_prompt_len
|
||||
}
|
||||
offset := 0
|
||||
has_cursor := i == self.cursor.Y
|
||||
for is_first := true; is_first || offset < len(line); is_first = false {
|
||||
l, width := wcswidth.TruncateToVisualLengthWithWidth(line[offset:], self.screen_width-plen)
|
||||
sl := ScreenLine{
|
||||
ParentLineNumber: i, OffsetInParentLine: offset,
|
||||
PromptLen: plen, TextLengthInCells: width,
|
||||
CursorCell: -1, Text: l, CursorTextPos: -1,
|
||||
}
|
||||
ans = append(ans, &sl)
|
||||
if has_cursor && !found_cursor && offset <= self.cursor.X && self.cursor.X <= offset+len(l) {
|
||||
found_cursor = true
|
||||
ctpos := self.cursor.X - offset
|
||||
ccell := plen + wcswidth.Stringwidth(l[:ctpos])
|
||||
if ccell >= self.screen_width {
|
||||
ans = append(ans, &ScreenLine{OffsetInParentLine: len(line)})
|
||||
} else {
|
||||
sl.CursorTextPos = ctpos
|
||||
sl.CursorCell = ccell
|
||||
}
|
||||
}
|
||||
plen = 0
|
||||
is_first = false
|
||||
offset += len(l)
|
||||
}
|
||||
}
|
||||
return ans
|
||||
}
|
||||
|
||||
func (self *Readline) redraw() {
|
||||
if self.screen_width == 0 {
|
||||
self.update_current_screen_size()
|
||||
@ -56,26 +82,36 @@ func (self *Readline) redraw() {
|
||||
}
|
||||
self.loop.QueueWriteString("\r")
|
||||
self.loop.ClearToEndOfScreen()
|
||||
line_with_cursor := 0
|
||||
y := 0
|
||||
for i, line := range self.lines {
|
||||
p := self.prompt
|
||||
cursor_x := -1
|
||||
cursor_y := 0
|
||||
move_cursor_up_by := 0
|
||||
self.loop.AllowLineWrapping(false)
|
||||
for i, sl := range self.get_screen_lines() {
|
||||
self.loop.QueueWriteString("\r")
|
||||
if i > 0 {
|
||||
y += 1
|
||||
self.loop.QueueWriteString("\r\n")
|
||||
p = self.continuation_prompt
|
||||
self.loop.QueueWriteString("\n")
|
||||
}
|
||||
if i == self.cursor.Y {
|
||||
line_with_cursor = y
|
||||
if sl.PromptLen > 0 {
|
||||
if i == 0 {
|
||||
self.loop.QueueWriteString(self.prompt)
|
||||
} else {
|
||||
self.loop.QueueWriteString(self.continuation_prompt)
|
||||
}
|
||||
y += self.write_line_with_prompt(line, p, self.screen_width)
|
||||
}
|
||||
self.loop.MoveCursorVertically(-y + line_with_cursor)
|
||||
line := self.lines[self.cursor.Y]
|
||||
plen := self.prompt_len
|
||||
if self.cursor.Y > 0 {
|
||||
plen = self.continuation_prompt_len
|
||||
self.loop.QueueWriteString(sl.Text)
|
||||
if sl.CursorCell > -1 {
|
||||
cursor_x = sl.CursorCell
|
||||
} else if cursor_x > -1 {
|
||||
move_cursor_up_by++
|
||||
}
|
||||
cursor_y++
|
||||
}
|
||||
self.loop.AllowLineWrapping(true)
|
||||
self.loop.MoveCursorVertically(-move_cursor_up_by)
|
||||
self.loop.QueueWriteString("\r")
|
||||
self.loop.MoveCursorHorizontally(cursor_x)
|
||||
self.cursor_y = 0
|
||||
if cursor_y > 0 {
|
||||
self.cursor_y = cursor_y - 1
|
||||
}
|
||||
line_with_cursor += self.move_cursor_to_text_position(plen+wcswidth.Stringwidth(line[:self.cursor.X]), self.screen_width)
|
||||
self.cursor_y = line_with_cursor
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user