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 (
|
require (
|
||||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924
|
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/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f
|
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 h1:DG4UyTVIujioxwJc8Zj8Nabz1L1wTgQ/xNBSQDfdP3I=
|
||||||
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
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 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
|||||||
@ -244,6 +244,14 @@ func (self *Loop) EndBracketedPaste() {
|
|||||||
self.QueueWriteString(BRACKETED_PASTE.EscapeCodeToReset())
|
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) {
|
func (self *Loop) Quit(exit_code int) {
|
||||||
self.exit_code = exit_code
|
self.exit_code = exit_code
|
||||||
self.keep_going = false
|
self.keep_going = false
|
||||||
|
|||||||
@ -162,6 +162,33 @@ func (self *Readline) move_cursor_right(amt uint, traverse_line_breaks bool) uin
|
|||||||
return amt_moved
|
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 {
|
func (self *Readline) move_to_start_of_line() bool {
|
||||||
if self.cursor.X > 0 {
|
if self.cursor.X > 0 {
|
||||||
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)
|
return self.perform_action(ActionAcceptInput, 1)
|
||||||
case ActionAcceptInput:
|
case ActionAcceptInput:
|
||||||
return ErrAcceptInput
|
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
|
return ErrCouldNotPerformAction
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"kitty/tools/tui/loop"
|
"kitty/tools/tui/loop"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = fmt.Print
|
var _ = fmt.Print
|
||||||
@ -56,6 +58,33 @@ func TestAddText(t *testing.T) {
|
|||||||
}, "abcd\nxy12\n34", "z", "abcd\nxy12\n34z")
|
}, "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) {
|
func TestCursorMovement(t *testing.T) {
|
||||||
dt := test_func(t)
|
dt := test_func(t)
|
||||||
|
|
||||||
|
|||||||
@ -47,6 +47,12 @@ const (
|
|||||||
ActionCursorRight
|
ActionCursorRight
|
||||||
ActionEndInput
|
ActionEndInput
|
||||||
ActionAcceptInput
|
ActionAcceptInput
|
||||||
|
ActionCursorUp
|
||||||
|
ActionHistoryPreviousOrCursorUp
|
||||||
|
ActionCursorDown
|
||||||
|
ActionHistoryNextOrCursorDown
|
||||||
|
ActionHistoryNext
|
||||||
|
ActionHistoryPrevious
|
||||||
)
|
)
|
||||||
|
|
||||||
type Readline struct {
|
type Readline struct {
|
||||||
|
|||||||
@ -9,26 +9,6 @@ import (
|
|||||||
|
|
||||||
var _ = fmt.Print
|
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() {
|
func (self *Readline) update_current_screen_size() {
|
||||||
screen_size, err := self.loop.ScreenSize()
|
screen_size, err := self.loop.ScreenSize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -44,6 +24,52 @@ func (self *Readline) update_current_screen_size() {
|
|||||||
self.screen_width = int(screen_size.WidthCells)
|
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() {
|
func (self *Readline) redraw() {
|
||||||
if self.screen_width == 0 {
|
if self.screen_width == 0 {
|
||||||
self.update_current_screen_size()
|
self.update_current_screen_size()
|
||||||
@ -56,26 +82,36 @@ func (self *Readline) redraw() {
|
|||||||
}
|
}
|
||||||
self.loop.QueueWriteString("\r")
|
self.loop.QueueWriteString("\r")
|
||||||
self.loop.ClearToEndOfScreen()
|
self.loop.ClearToEndOfScreen()
|
||||||
line_with_cursor := 0
|
cursor_x := -1
|
||||||
y := 0
|
cursor_y := 0
|
||||||
for i, line := range self.lines {
|
move_cursor_up_by := 0
|
||||||
p := self.prompt
|
self.loop.AllowLineWrapping(false)
|
||||||
|
for i, sl := range self.get_screen_lines() {
|
||||||
|
self.loop.QueueWriteString("\r")
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
y += 1
|
self.loop.QueueWriteString("\n")
|
||||||
self.loop.QueueWriteString("\r\n")
|
|
||||||
p = self.continuation_prompt
|
|
||||||
}
|
}
|
||||||
if i == self.cursor.Y {
|
if sl.PromptLen > 0 {
|
||||||
line_with_cursor = y
|
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.QueueWriteString(sl.Text)
|
||||||
|
if sl.CursorCell > -1 {
|
||||||
|
cursor_x = sl.CursorCell
|
||||||
|
} else if cursor_x > -1 {
|
||||||
|
move_cursor_up_by++
|
||||||
|
}
|
||||||
|
cursor_y++
|
||||||
}
|
}
|
||||||
self.loop.MoveCursorVertically(-y + line_with_cursor)
|
self.loop.AllowLineWrapping(true)
|
||||||
line := self.lines[self.cursor.Y]
|
self.loop.MoveCursorVertically(-move_cursor_up_by)
|
||||||
plen := self.prompt_len
|
self.loop.QueueWriteString("\r")
|
||||||
if self.cursor.Y > 0 {
|
self.loop.MoveCursorHorizontally(cursor_x)
|
||||||
plen = self.continuation_prompt_len
|
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