Code to erase character ranges
This commit is contained in:
parent
5e5cae8391
commit
eff239a195
@ -16,8 +16,8 @@ func (self *Readline) text_upto_cursor_pos() string {
|
||||
buf := strings.Builder{}
|
||||
buf.Grow(1024)
|
||||
for i, line := range self.lines {
|
||||
if i == self.cursor_line {
|
||||
buf.WriteString(line[:self.cursor_pos_in_line])
|
||||
if i == self.cursor.Y {
|
||||
buf.WriteString(line[:self.cursor.X])
|
||||
break
|
||||
} else {
|
||||
buf.WriteString(line)
|
||||
@ -31,10 +31,10 @@ func (self *Readline) text_after_cursor_pos() string {
|
||||
buf := strings.Builder{}
|
||||
buf.Grow(1024)
|
||||
for i, line := range self.lines {
|
||||
if i == self.cursor_line {
|
||||
buf.WriteString(line[self.cursor_pos_in_line:])
|
||||
if i == self.cursor.Y {
|
||||
buf.WriteString(line[self.cursor.X:])
|
||||
buf.WriteString("\n")
|
||||
} else if i > self.cursor_line {
|
||||
} else if i > self.cursor.Y {
|
||||
buf.WriteString(line)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
@ -50,30 +50,30 @@ func (self *Readline) all_text() string {
|
||||
|
||||
func (self *Readline) add_text(text string) {
|
||||
new_lines := make([]string, 0, len(self.lines)+4)
|
||||
new_lines = append(new_lines, self.lines[:self.cursor_line]...)
|
||||
new_lines = append(new_lines, self.lines[:self.cursor.Y]...)
|
||||
var lines_after []string
|
||||
if len(self.lines) > self.cursor_line+1 {
|
||||
lines_after = self.lines[self.cursor_line+1:]
|
||||
if len(self.lines) > self.cursor.Y+1 {
|
||||
lines_after = self.lines[self.cursor.Y+1:]
|
||||
}
|
||||
has_trailing_newline := strings.HasSuffix(text, "\n")
|
||||
|
||||
add_line_break := func(line string) {
|
||||
new_lines = append(new_lines, line)
|
||||
self.cursor_pos_in_line = len(line)
|
||||
self.cursor_line += 1
|
||||
self.cursor.X = len(line)
|
||||
self.cursor.Y += 1
|
||||
}
|
||||
cline := self.lines[self.cursor_line]
|
||||
before_first_line := cline[:self.cursor_pos_in_line]
|
||||
cline := self.lines[self.cursor.Y]
|
||||
before_first_line := cline[:self.cursor.X]
|
||||
after_first_line := ""
|
||||
if self.cursor_pos_in_line < len(cline) {
|
||||
after_first_line = cline[self.cursor_pos_in_line:]
|
||||
if self.cursor.X < len(cline) {
|
||||
after_first_line = cline[self.cursor.X:]
|
||||
}
|
||||
for i, line := range utils.Splitlines(text) {
|
||||
if i > 0 {
|
||||
add_line_break(line)
|
||||
} else {
|
||||
line := before_first_line + line
|
||||
self.cursor_pos_in_line = len(line)
|
||||
self.cursor.X = len(line)
|
||||
new_lines = append(new_lines, line)
|
||||
}
|
||||
}
|
||||
@ -95,27 +95,27 @@ func (self *Readline) add_text(text string) {
|
||||
func (self *Readline) move_cursor_left(amt uint, traverse_line_breaks bool) uint {
|
||||
var amt_moved uint
|
||||
for ; amt > 0; amt -= 1 {
|
||||
if self.cursor_pos_in_line == 0 {
|
||||
if !traverse_line_breaks || self.cursor_line == 0 {
|
||||
if self.cursor.X == 0 {
|
||||
if !traverse_line_breaks || self.cursor.Y == 0 {
|
||||
return amt_moved
|
||||
}
|
||||
self.cursor_line -= 1
|
||||
self.cursor_pos_in_line = len(self.lines[self.cursor_pos_in_line])
|
||||
self.cursor.Y -= 1
|
||||
self.cursor.X = len(self.lines[self.cursor.Y])
|
||||
amt_moved += 1
|
||||
continue
|
||||
}
|
||||
// This is an extremely inefficient algorithm but it does not matter since
|
||||
// lines are not large.
|
||||
line := self.lines[self.cursor_line]
|
||||
runes := []rune(line[:self.cursor_pos_in_line])
|
||||
orig_width := wcswidth.Stringwidth(line[:self.cursor_pos_in_line])
|
||||
line := self.lines[self.cursor.Y]
|
||||
runes := []rune(line[:self.cursor.X])
|
||||
orig_width := wcswidth.Stringwidth(line[:self.cursor.X])
|
||||
current_width := orig_width
|
||||
for current_width == orig_width && len(runes) > 0 {
|
||||
runes = runes[:len(runes)-1]
|
||||
s := string(runes)
|
||||
current_width = wcswidth.Stringwidth(s)
|
||||
}
|
||||
self.cursor_pos_in_line = len(string(runes))
|
||||
self.cursor.X = len(string(runes))
|
||||
amt_moved += 1
|
||||
}
|
||||
return amt_moved
|
||||
@ -124,21 +124,21 @@ func (self *Readline) move_cursor_left(amt uint, traverse_line_breaks bool) uint
|
||||
func (self *Readline) move_cursor_right(amt uint, traverse_line_breaks bool) uint {
|
||||
var amt_moved uint
|
||||
for ; amt > 0; amt -= 1 {
|
||||
line := self.lines[self.cursor_line]
|
||||
if self.cursor_pos_in_line >= len(line) {
|
||||
if !traverse_line_breaks || self.cursor_line == len(self.lines)-1 {
|
||||
line := self.lines[self.cursor.Y]
|
||||
if self.cursor.X >= len(line) {
|
||||
if !traverse_line_breaks || self.cursor.Y == len(self.lines)-1 {
|
||||
return amt_moved
|
||||
}
|
||||
self.cursor_line += 1
|
||||
self.cursor_pos_in_line = 0
|
||||
self.cursor.Y += 1
|
||||
self.cursor.X = 0
|
||||
amt_moved += 1
|
||||
continue
|
||||
}
|
||||
// This is an extremely inefficient algorithm but it does not matter since
|
||||
// lines are not large.
|
||||
before_runes := []rune(line[:self.cursor_pos_in_line])
|
||||
after_runes := []rune(line[self.cursor_pos_in_line:])
|
||||
orig_width := wcswidth.Stringwidth(line[:self.cursor_pos_in_line])
|
||||
before_runes := []rune(line[:self.cursor.X])
|
||||
after_runes := []rune(line[self.cursor.X:])
|
||||
orig_width := wcswidth.Stringwidth(line[:self.cursor.X])
|
||||
current_width := orig_width
|
||||
for current_width == orig_width && len(after_runes) > 0 {
|
||||
before_runes = append(before_runes, after_runes[0])
|
||||
@ -155,8 +155,106 @@ func (self *Readline) move_cursor_right(amt uint, traverse_line_breaks bool) uin
|
||||
after_runes = after_runes[1:]
|
||||
before_runes = q
|
||||
}
|
||||
self.cursor_pos_in_line = len(string(before_runes))
|
||||
self.cursor.X = len(string(before_runes))
|
||||
amt_moved += 1
|
||||
}
|
||||
return amt_moved
|
||||
}
|
||||
|
||||
func (self *Readline) move_to_start_of_line() bool {
|
||||
if self.cursor.X > 0 {
|
||||
self.cursor.X = 0
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *Readline) move_to_end_of_line() bool {
|
||||
line := self.lines[self.cursor.Y]
|
||||
if self.cursor.X >= len(line) {
|
||||
return false
|
||||
}
|
||||
self.cursor.X = len(line)
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Readline) move_to_start() bool {
|
||||
if self.cursor.Y == 0 && self.cursor.X == 0 {
|
||||
return false
|
||||
}
|
||||
self.cursor.Y = 0
|
||||
self.move_to_start_of_line()
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Readline) move_to_end() bool {
|
||||
line := self.lines[self.cursor.Y]
|
||||
if self.cursor.Y == len(self.lines)-1 && self.cursor.X >= len(line) {
|
||||
return false
|
||||
}
|
||||
self.cursor.Y = len(self.lines) - 1
|
||||
self.move_to_end_of_line()
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Readline) erase_between(start, end Position) {
|
||||
if end.Less(start) {
|
||||
start, end = end, start
|
||||
}
|
||||
if start.Y == end.Y {
|
||||
line := self.lines[start.Y]
|
||||
self.lines[start.Y] = line[:start.X] + line[end.X:]
|
||||
if self.cursor.Y == start.Y && self.cursor.X >= start.X {
|
||||
if self.cursor.X < end.X {
|
||||
self.cursor.X = start.X
|
||||
} else {
|
||||
self.cursor.X -= end.X - start.X
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
lines := make([]string, 0, len(self.lines))
|
||||
for i, line := range self.lines {
|
||||
if i < start.Y || i > end.Y {
|
||||
lines = append(lines, line)
|
||||
} else if i == start.Y {
|
||||
lines = append(lines, line[:start.X])
|
||||
if self.cursor.Y == i && self.cursor.X > start.X {
|
||||
self.cursor.X = start.X
|
||||
}
|
||||
} else if i == end.Y {
|
||||
lines[len(lines)-1] += line[end.X:]
|
||||
if i == self.cursor.Y {
|
||||
self.cursor.Y = start.Y
|
||||
if self.cursor.X < end.X {
|
||||
self.cursor.X = start.X
|
||||
} else {
|
||||
self.cursor.X -= end.X - start.X
|
||||
}
|
||||
}
|
||||
} else if i == self.cursor.Y {
|
||||
self.cursor = start
|
||||
}
|
||||
}
|
||||
self.lines = lines
|
||||
}
|
||||
|
||||
func (self *Readline) erase_chars_before_cursor(amt uint, traverse_line_breaks bool) uint {
|
||||
pos := self.cursor
|
||||
num := self.move_cursor_left(amt, traverse_line_breaks)
|
||||
if num == 0 {
|
||||
return num
|
||||
}
|
||||
self.erase_between(self.cursor, pos)
|
||||
return num
|
||||
}
|
||||
|
||||
func (self *Readline) erase_chars_after_cursor(amt uint, traverse_line_breaks bool) uint {
|
||||
pos := self.cursor
|
||||
num := self.move_cursor_right(amt, traverse_line_breaks)
|
||||
if num == 0 {
|
||||
return num
|
||||
}
|
||||
self.erase_between(pos, self.cursor)
|
||||
return num
|
||||
}
|
||||
|
||||
@ -43,15 +43,15 @@ func TestAddText(t *testing.T) {
|
||||
dt("test", nil, "test", "", "test")
|
||||
dt("1234\n", nil, "1234\n", "", "1234\n")
|
||||
dt("abcd", func(rl *Readline) {
|
||||
rl.cursor_pos_in_line = 2
|
||||
rl.cursor.X = 2
|
||||
rl.add_text("12")
|
||||
}, "ab12", "cd", "ab12cd")
|
||||
dt("abcd", func(rl *Readline) {
|
||||
rl.cursor_pos_in_line = 2
|
||||
rl.cursor.X = 2
|
||||
rl.add_text("12\n34")
|
||||
}, "ab12\n34", "cd", "ab12\n34cd")
|
||||
dt("abcd\nxyz", func(rl *Readline) {
|
||||
rl.cursor_pos_in_line = 2
|
||||
rl.cursor.X = 2
|
||||
rl.add_text("12\n34")
|
||||
}, "abcd\nxy12\n34", "z", "abcd\nxy12\n34z")
|
||||
}
|
||||
@ -88,8 +88,8 @@ func TestCursorMovement(t *testing.T) {
|
||||
}, "one", "à")
|
||||
|
||||
right := func(rl *Readline, amt uint, moved_amt uint, traverse_line_breaks bool) {
|
||||
rl.cursor_line = 0
|
||||
rl.cursor_pos_in_line = 0
|
||||
rl.cursor.Y = 0
|
||||
rl.cursor.X = 0
|
||||
actual := rl.move_cursor_right(amt, traverse_line_breaks)
|
||||
if actual != moved_amt {
|
||||
t.Fatalf("Failed to move cursor by %#v\nactual != expected: %#v != %#v", amt, actual, moved_amt)
|
||||
@ -111,3 +111,69 @@ func TestCursorMovement(t *testing.T) {
|
||||
right(rl, 1, 1, false)
|
||||
}, "à", "b")
|
||||
}
|
||||
|
||||
func TestEraseChars(t *testing.T) {
|
||||
dt := test_func(t)
|
||||
|
||||
backspace := func(rl *Readline, amt uint, erased_amt uint, traverse_line_breaks bool) {
|
||||
actual := rl.erase_chars_before_cursor(amt, traverse_line_breaks)
|
||||
if actual != erased_amt {
|
||||
t.Fatalf("Failed to move cursor by %#v\nactual != expected: %d != %d", amt, actual, erased_amt)
|
||||
}
|
||||
}
|
||||
dt("one\ntwo", func(rl *Readline) {
|
||||
backspace(rl, 2, 2, false)
|
||||
}, "one\nt", "")
|
||||
dt("one\ntwo", func(rl *Readline) {
|
||||
rl.cursor.X = 1
|
||||
backspace(rl, 2, 1, false)
|
||||
}, "one\n", "wo")
|
||||
dt("one\ntwo", func(rl *Readline) {
|
||||
rl.cursor.X = 1
|
||||
backspace(rl, 2, 2, true)
|
||||
}, "one", "wo")
|
||||
dt("a😀", func(rl *Readline) {
|
||||
backspace(rl, 1, 1, false)
|
||||
}, "a", "")
|
||||
dt("bà", func(rl *Readline) {
|
||||
backspace(rl, 1, 1, false)
|
||||
}, "b", "")
|
||||
|
||||
del := func(rl *Readline, amt uint, erased_amt uint, traverse_line_breaks bool) {
|
||||
rl.cursor.Y = 0
|
||||
rl.cursor.X = 0
|
||||
actual := rl.erase_chars_after_cursor(amt, traverse_line_breaks)
|
||||
if actual != erased_amt {
|
||||
t.Fatalf("Failed to move cursor by %#v\nactual != expected: %d != %d", amt, actual, erased_amt)
|
||||
}
|
||||
}
|
||||
dt("one\ntwo", func(rl *Readline) {
|
||||
del(rl, 2, 2, false)
|
||||
}, "", "e\ntwo")
|
||||
dt("😀a", func(rl *Readline) {
|
||||
del(rl, 1, 1, false)
|
||||
}, "", "a")
|
||||
dt("àb", func(rl *Readline) {
|
||||
del(rl, 1, 1, false)
|
||||
}, "", "b")
|
||||
|
||||
dt("one\ntwo\nthree", func(rl *Readline) {
|
||||
rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
|
||||
}, "oree", "")
|
||||
dt("one\ntwo\nthree", func(rl *Readline) {
|
||||
rl.cursor.X = 1
|
||||
rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
|
||||
}, "o", "ree")
|
||||
dt("one\ntwo\nthree", func(rl *Readline) {
|
||||
rl.cursor = Position{X: 1, Y: 1}
|
||||
rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
|
||||
}, "o", "ree")
|
||||
dt("one\ntwo\nthree", func(rl *Readline) {
|
||||
rl.cursor = Position{X: 1, Y: 0}
|
||||
rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
|
||||
}, "o", "ree")
|
||||
dt("one\ntwo\nthree", func(rl *Readline) {
|
||||
rl.cursor = Position{X: 0, Y: 0}
|
||||
rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
|
||||
}, "", "oree")
|
||||
}
|
||||
|
||||
@ -21,6 +21,15 @@ type RlInit struct {
|
||||
DontMarkPrompts bool
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
|
||||
func (self Position) Less(other Position) bool {
|
||||
return self.Y < other.Y || (self.Y == other.Y && self.X < other.X)
|
||||
}
|
||||
|
||||
type Readline struct {
|
||||
prompt string
|
||||
prompt_len int
|
||||
@ -29,14 +38,12 @@ type Readline struct {
|
||||
mark_prompts bool
|
||||
loop *loop.Loop
|
||||
|
||||
// The number of lines after the initial line
|
||||
// The number of lines after the initial line on the screen
|
||||
cursor_y int
|
||||
// Input lines
|
||||
lines []string
|
||||
// The line the cursor is at currently
|
||||
cursor_line int
|
||||
// The offset into the text of the cursor line
|
||||
cursor_pos_in_line int
|
||||
// The cursor position in the text
|
||||
cursor Position
|
||||
}
|
||||
|
||||
func New(loop *loop.Loop, r RlInit) *Readline {
|
||||
|
||||
@ -46,13 +46,13 @@ func (self *Readline) redraw() {
|
||||
p = self.continuation_prompt
|
||||
}
|
||||
num_lines := self.write_line_with_prompt(line, p, int(screen_size.WidthCells))
|
||||
if i == self.cursor_line {
|
||||
if i == self.cursor.Y {
|
||||
line_with_cursor = y
|
||||
}
|
||||
y += num_lines
|
||||
}
|
||||
self.loop.MoveCursorVertically(-y + line_with_cursor)
|
||||
line := self.lines[self.cursor_line]
|
||||
line_with_cursor += self.move_cursor_to_text_position(wcswidth.Stringwidth(line[:self.cursor_pos_in_line]), int(screen_size.WidthCells))
|
||||
line := self.lines[self.cursor.Y]
|
||||
line_with_cursor += self.move_cursor_to_text_position(wcswidth.Stringwidth(line[:self.cursor.X]), int(screen_size.WidthCells))
|
||||
self.cursor_y = line_with_cursor
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user