more work on history search
This commit is contained in:
parent
ffea66357a
commit
0c82832356
@ -17,9 +17,9 @@ var _ = fmt.Print
|
||||
func (self *Readline) text_upto_cursor_pos() string {
|
||||
buf := strings.Builder{}
|
||||
buf.Grow(1024)
|
||||
for i, line := range self.lines {
|
||||
if i == self.cursor.Y {
|
||||
buf.WriteString(line[:utils.Min(len(line), self.cursor.X)])
|
||||
for i, line := range self.input_state.lines {
|
||||
if i == self.input_state.cursor.Y {
|
||||
buf.WriteString(line[:utils.Min(len(line), self.input_state.cursor.X)])
|
||||
break
|
||||
} else {
|
||||
buf.WriteString(line)
|
||||
@ -32,11 +32,11 @@ func (self *Readline) text_upto_cursor_pos() string {
|
||||
func (self *Readline) text_after_cursor_pos() string {
|
||||
buf := strings.Builder{}
|
||||
buf.Grow(1024)
|
||||
for i, line := range self.lines {
|
||||
if i == self.cursor.Y {
|
||||
buf.WriteString(line[utils.Min(len(line), self.cursor.X):])
|
||||
for i, line := range self.input_state.lines {
|
||||
if i == self.input_state.cursor.Y {
|
||||
buf.WriteString(line[utils.Min(len(line), self.input_state.cursor.X):])
|
||||
buf.WriteString("\n")
|
||||
} else if i > self.cursor.Y {
|
||||
} else if i > self.input_state.cursor.Y {
|
||||
buf.WriteString(line)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
@ -49,35 +49,35 @@ func (self *Readline) text_after_cursor_pos() string {
|
||||
}
|
||||
|
||||
func (self *Readline) all_text() string {
|
||||
return strings.Join(self.lines, "\n")
|
||||
return strings.Join(self.input_state.lines, "\n")
|
||||
}
|
||||
|
||||
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.Y]...)
|
||||
new_lines := make([]string, 0, len(self.input_state.lines)+4)
|
||||
new_lines = append(new_lines, self.input_state.lines[:self.input_state.cursor.Y]...)
|
||||
var lines_after []string
|
||||
if len(self.lines) > self.cursor.Y+1 {
|
||||
lines_after = self.lines[self.cursor.Y+1:]
|
||||
if len(self.input_state.lines) > self.input_state.cursor.Y+1 {
|
||||
lines_after = self.input_state.lines[self.input_state.cursor.Y+1:]
|
||||
}
|
||||
has_trailing_newline := strings.HasSuffix(text, "\n")
|
||||
|
||||
add_line_break := func(line string) {
|
||||
new_lines = append(new_lines, line)
|
||||
self.cursor.X = len(line)
|
||||
self.cursor.Y += 1
|
||||
self.input_state.cursor.X = len(line)
|
||||
self.input_state.cursor.Y += 1
|
||||
}
|
||||
cline := self.lines[self.cursor.Y]
|
||||
before_first_line := cline[:self.cursor.X]
|
||||
cline := self.input_state.lines[self.input_state.cursor.Y]
|
||||
before_first_line := cline[:self.input_state.cursor.X]
|
||||
after_first_line := ""
|
||||
if self.cursor.X < len(cline) {
|
||||
after_first_line = cline[self.cursor.X:]
|
||||
if self.input_state.cursor.X < len(cline) {
|
||||
after_first_line = cline[self.input_state.cursor.X:]
|
||||
}
|
||||
for i, line := range utils.Splitlines(text) {
|
||||
if i > 0 {
|
||||
add_line_break(line)
|
||||
} else {
|
||||
line := before_first_line + line
|
||||
self.cursor.X = len(line)
|
||||
self.input_state.cursor.X = len(line)
|
||||
new_lines = append(new_lines, line)
|
||||
}
|
||||
}
|
||||
@ -93,23 +93,23 @@ func (self *Readline) add_text(text string) {
|
||||
if len(lines_after) > 0 {
|
||||
new_lines = append(new_lines, lines_after...)
|
||||
}
|
||||
self.lines = new_lines
|
||||
self.input_state.lines = new_lines
|
||||
}
|
||||
|
||||
func (self *Readline) move_cursor_left(amt uint, traverse_line_breaks bool) (amt_moved uint) {
|
||||
for amt_moved < amt {
|
||||
if self.cursor.X == 0 {
|
||||
if !traverse_line_breaks || self.cursor.Y == 0 {
|
||||
if self.input_state.cursor.X == 0 {
|
||||
if !traverse_line_breaks || self.input_state.cursor.Y == 0 {
|
||||
return amt_moved
|
||||
}
|
||||
self.cursor.Y -= 1
|
||||
self.cursor.X = len(self.lines[self.cursor.Y])
|
||||
self.input_state.cursor.Y -= 1
|
||||
self.input_state.cursor.X = len(self.input_state.lines[self.input_state.cursor.Y])
|
||||
amt_moved++
|
||||
continue
|
||||
}
|
||||
line := self.lines[self.cursor.Y]
|
||||
for ci := wcswidth.NewCellIterator(line[:self.cursor.X]).GotoEnd(); amt_moved < amt && ci.Backward(); amt_moved++ {
|
||||
self.cursor.X -= len(ci.Current())
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
for ci := wcswidth.NewCellIterator(line[:self.input_state.cursor.X]).GotoEnd(); amt_moved < amt && ci.Backward(); amt_moved++ {
|
||||
self.input_state.cursor.X -= len(ci.Current())
|
||||
}
|
||||
}
|
||||
return amt_moved
|
||||
@ -117,19 +117,19 @@ func (self *Readline) move_cursor_left(amt uint, traverse_line_breaks bool) (amt
|
||||
|
||||
func (self *Readline) move_cursor_right(amt uint, traverse_line_breaks bool) (amt_moved uint) {
|
||||
for amt_moved < amt {
|
||||
line := self.lines[self.cursor.Y]
|
||||
if self.cursor.X >= len(line) {
|
||||
if !traverse_line_breaks || self.cursor.Y == len(self.lines)-1 {
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
if self.input_state.cursor.X >= len(line) {
|
||||
if !traverse_line_breaks || self.input_state.cursor.Y == len(self.input_state.lines)-1 {
|
||||
return amt_moved
|
||||
}
|
||||
self.cursor.Y += 1
|
||||
self.cursor.X = 0
|
||||
self.input_state.cursor.Y += 1
|
||||
self.input_state.cursor.X = 0
|
||||
amt_moved++
|
||||
continue
|
||||
}
|
||||
|
||||
for ci := wcswidth.NewCellIterator(line[self.cursor.X:]); amt_moved < amt && ci.Forward(); amt_moved++ {
|
||||
self.cursor.X += len(ci.Current())
|
||||
for ci := wcswidth.NewCellIterator(line[self.input_state.cursor.X:]); amt_moved < amt && ci.Forward(); amt_moved++ {
|
||||
self.input_state.cursor.X += len(ci.Current())
|
||||
}
|
||||
}
|
||||
return amt_moved
|
||||
@ -138,9 +138,9 @@ func (self *Readline) move_cursor_right(amt uint, traverse_line_breaks bool) (am
|
||||
func (self *Readline) move_cursor_to_target_line(source_line, target_line *ScreenLine) {
|
||||
if source_line != target_line {
|
||||
visual_distance_into_text := source_line.CursorCell - source_line.Prompt.Length
|
||||
self.cursor.Y = target_line.ParentLineNumber
|
||||
self.input_state.cursor.Y = target_line.ParentLineNumber
|
||||
tp := wcswidth.TruncateToVisualLength(target_line.Text, visual_distance_into_text)
|
||||
self.cursor.X = target_line.OffsetInParentLine + len(tp)
|
||||
self.input_state.cursor.X = target_line.OffsetInParentLine + len(tp)
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,37 +173,37 @@ func (self *Readline) move_cursor_down(amt uint) uint {
|
||||
}
|
||||
|
||||
func (self *Readline) move_to_start_of_line() bool {
|
||||
if self.cursor.X > 0 {
|
||||
self.cursor.X = 0
|
||||
if self.input_state.cursor.X > 0 {
|
||||
self.input_state.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) {
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
if self.input_state.cursor.X >= len(line) {
|
||||
return false
|
||||
}
|
||||
self.cursor.X = len(line)
|
||||
self.input_state.cursor.X = len(line)
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Readline) move_to_start() bool {
|
||||
if self.cursor.Y == 0 && self.cursor.X == 0 {
|
||||
if self.input_state.cursor.Y == 0 && self.input_state.cursor.X == 0 {
|
||||
return false
|
||||
}
|
||||
self.cursor.Y = 0
|
||||
self.input_state.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) {
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
if self.input_state.cursor.Y == len(self.input_state.lines)-1 && self.input_state.cursor.X >= len(line) {
|
||||
return false
|
||||
}
|
||||
self.cursor.Y = len(self.lines) - 1
|
||||
self.input_state.cursor.Y = len(self.input_state.lines) - 1
|
||||
self.move_to_end_of_line()
|
||||
return true
|
||||
}
|
||||
@ -214,68 +214,68 @@ func (self *Readline) erase_between(start, end Position) string {
|
||||
}
|
||||
buf := strings.Builder{}
|
||||
if start.Y == end.Y {
|
||||
line := self.lines[start.Y]
|
||||
line := self.input_state.lines[start.Y]
|
||||
buf.WriteString(line[start.X:end.X])
|
||||
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
|
||||
self.input_state.lines[start.Y] = line[:start.X] + line[end.X:]
|
||||
if self.input_state.cursor.Y == start.Y && self.input_state.cursor.X >= start.X {
|
||||
if self.input_state.cursor.X < end.X {
|
||||
self.input_state.cursor.X = start.X
|
||||
} else {
|
||||
self.cursor.X -= end.X - start.X
|
||||
self.input_state.cursor.X -= end.X - start.X
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
lines := make([]string, 0, len(self.lines))
|
||||
for i, line := range self.lines {
|
||||
lines := make([]string, 0, len(self.input_state.lines))
|
||||
for i, line := range self.input_state.lines {
|
||||
if i < start.Y || i > end.Y {
|
||||
lines = append(lines, line)
|
||||
} else if i == start.Y {
|
||||
lines = append(lines, line[:start.X])
|
||||
buf.WriteString(line[start.X:])
|
||||
if self.cursor.Y == i && self.cursor.X > start.X {
|
||||
self.cursor.X = start.X
|
||||
if self.input_state.cursor.Y == i && self.input_state.cursor.X > start.X {
|
||||
self.input_state.cursor.X = start.X
|
||||
}
|
||||
} else if i == end.Y {
|
||||
lines[len(lines)-1] += line[end.X:]
|
||||
buf.WriteString(line[:end.X])
|
||||
if i == self.cursor.Y {
|
||||
self.cursor.Y = start.Y
|
||||
if self.cursor.X < end.X {
|
||||
self.cursor.X = start.X
|
||||
if i == self.input_state.cursor.Y {
|
||||
self.input_state.cursor.Y = start.Y
|
||||
if self.input_state.cursor.X < end.X {
|
||||
self.input_state.cursor.X = start.X
|
||||
} else {
|
||||
self.cursor.X -= end.X - start.X
|
||||
self.input_state.cursor.X -= end.X - start.X
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if i == self.cursor.Y {
|
||||
self.cursor = start
|
||||
if i == self.input_state.cursor.Y {
|
||||
self.input_state.cursor = start
|
||||
}
|
||||
buf.WriteString(line)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
}
|
||||
self.lines = lines
|
||||
self.input_state.lines = lines
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (self *Readline) erase_chars_before_cursor(amt uint, traverse_line_breaks bool) uint {
|
||||
pos := self.cursor
|
||||
pos := self.input_state.cursor
|
||||
num := self.move_cursor_left(amt, traverse_line_breaks)
|
||||
if num == 0 {
|
||||
return num
|
||||
}
|
||||
self.erase_between(self.cursor, pos)
|
||||
self.erase_between(self.input_state.cursor, pos)
|
||||
return num
|
||||
}
|
||||
|
||||
func (self *Readline) erase_chars_after_cursor(amt uint, traverse_line_breaks bool) uint {
|
||||
pos := self.cursor
|
||||
pos := self.input_state.cursor
|
||||
num := self.move_cursor_right(amt, traverse_line_breaks)
|
||||
if num == 0 {
|
||||
return num
|
||||
}
|
||||
self.erase_between(pos, self.cursor)
|
||||
self.erase_between(pos, self.input_state.cursor)
|
||||
return num
|
||||
}
|
||||
|
||||
@ -292,9 +292,9 @@ func (self *Readline) move_to_end_of_word(amt uint, traverse_line_breaks bool, i
|
||||
if amt == 0 {
|
||||
return 0
|
||||
}
|
||||
line := self.lines[self.cursor.Y]
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
in_word := false
|
||||
ci := wcswidth.NewCellIterator(line[self.cursor.X:])
|
||||
ci := wcswidth.NewCellIterator(line[self.input_state.cursor.X:])
|
||||
sz := 0
|
||||
|
||||
for ci.Forward() {
|
||||
@ -304,7 +304,7 @@ func (self *Readline) move_to_end_of_word(amt uint, traverse_line_breaks bool, i
|
||||
if current_is_word_char {
|
||||
in_word = true
|
||||
} else if in_word {
|
||||
self.cursor.X += plen
|
||||
self.input_state.cursor.X += plen
|
||||
amt--
|
||||
num_of_words_moved++
|
||||
if amt == 0 {
|
||||
@ -318,9 +318,9 @@ func (self *Readline) move_to_end_of_word(amt uint, traverse_line_breaks bool, i
|
||||
num_of_words_moved++
|
||||
}
|
||||
if amt > 0 {
|
||||
if traverse_line_breaks && self.cursor.Y < len(self.lines)-1 {
|
||||
self.cursor.Y++
|
||||
self.cursor.X = 0
|
||||
if traverse_line_breaks && self.input_state.cursor.Y < len(self.input_state.lines)-1 {
|
||||
self.input_state.cursor.Y++
|
||||
self.input_state.cursor.X = 0
|
||||
num_of_words_moved += self.move_to_end_of_word(amt, traverse_line_breaks, is_part_of_word)
|
||||
}
|
||||
}
|
||||
@ -331,9 +331,9 @@ func (self *Readline) move_to_start_of_word(amt uint, traverse_line_breaks bool,
|
||||
if amt == 0 {
|
||||
return 0
|
||||
}
|
||||
line := self.lines[self.cursor.Y]
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
in_word := false
|
||||
ci := wcswidth.NewCellIterator(line[:self.cursor.X]).GotoEnd()
|
||||
ci := wcswidth.NewCellIterator(line[:self.input_state.cursor.X]).GotoEnd()
|
||||
sz := 0
|
||||
|
||||
for ci.Backward() {
|
||||
@ -343,7 +343,7 @@ func (self *Readline) move_to_start_of_word(amt uint, traverse_line_breaks bool,
|
||||
if current_is_word_char {
|
||||
in_word = true
|
||||
} else if in_word {
|
||||
self.cursor.X -= plen
|
||||
self.input_state.cursor.X -= plen
|
||||
amt--
|
||||
num_of_words_moved++
|
||||
if amt == 0 {
|
||||
@ -357,9 +357,9 @@ func (self *Readline) move_to_start_of_word(amt uint, traverse_line_breaks bool,
|
||||
num_of_words_moved++
|
||||
}
|
||||
if amt > 0 {
|
||||
if traverse_line_breaks && self.cursor.Y > 0 {
|
||||
self.cursor.Y--
|
||||
self.cursor.X = len(self.lines[self.cursor.Y])
|
||||
if traverse_line_breaks && self.input_state.cursor.Y > 0 {
|
||||
self.input_state.cursor.Y--
|
||||
self.input_state.cursor.X = len(self.input_state.lines[self.input_state.cursor.Y])
|
||||
num_of_words_moved += self.move_to_start_of_word(amt, traverse_line_breaks, has_word_chars)
|
||||
}
|
||||
}
|
||||
@ -375,40 +375,40 @@ func (self *Readline) kill_text(text string) {
|
||||
}
|
||||
|
||||
func (self *Readline) kill_to_end_of_line() bool {
|
||||
line := self.lines[self.cursor.Y]
|
||||
if self.cursor.X >= len(line) {
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
if self.input_state.cursor.X >= len(line) {
|
||||
return false
|
||||
}
|
||||
self.lines[self.cursor.Y] = line[:self.cursor.X]
|
||||
self.kill_text(line[self.cursor.X:])
|
||||
self.input_state.lines[self.input_state.cursor.Y] = line[:self.input_state.cursor.X]
|
||||
self.kill_text(line[self.input_state.cursor.X:])
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Readline) kill_to_start_of_line() bool {
|
||||
line := self.lines[self.cursor.Y]
|
||||
if self.cursor.X <= 0 {
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
if self.input_state.cursor.X <= 0 {
|
||||
return false
|
||||
}
|
||||
self.lines[self.cursor.Y] = line[self.cursor.X:]
|
||||
self.kill_text(line[:self.cursor.X])
|
||||
self.cursor.X = 0
|
||||
self.input_state.lines[self.input_state.cursor.Y] = line[self.input_state.cursor.X:]
|
||||
self.kill_text(line[:self.input_state.cursor.X])
|
||||
self.input_state.cursor.X = 0
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Readline) kill_next_word(amt uint, traverse_line_breaks bool) (num_killed uint) {
|
||||
before := self.cursor
|
||||
before := self.input_state.cursor
|
||||
num_killed = self.move_to_end_of_word(amt, traverse_line_breaks, has_word_chars)
|
||||
if num_killed > 0 {
|
||||
self.kill_text(self.erase_between(before, self.cursor))
|
||||
self.kill_text(self.erase_between(before, self.input_state.cursor))
|
||||
}
|
||||
return num_killed
|
||||
}
|
||||
|
||||
func (self *Readline) kill_previous_word(amt uint, traverse_line_breaks bool) (num_killed uint) {
|
||||
before := self.cursor
|
||||
before := self.input_state.cursor
|
||||
num_killed = self.move_to_start_of_word(amt, traverse_line_breaks, has_word_chars)
|
||||
if num_killed > 0 {
|
||||
self.kill_text(self.erase_between(self.cursor, before))
|
||||
self.kill_text(self.erase_between(self.input_state.cursor, before))
|
||||
}
|
||||
return num_killed
|
||||
}
|
||||
@ -423,17 +423,17 @@ func has_no_space_chars(text string) bool {
|
||||
}
|
||||
|
||||
func (self *Readline) kill_previous_space_delimited_word(amt uint, traverse_line_breaks bool) (num_killed uint) {
|
||||
before := self.cursor
|
||||
before := self.input_state.cursor
|
||||
num_killed = self.move_to_start_of_word(amt, traverse_line_breaks, has_no_space_chars)
|
||||
if num_killed > 0 {
|
||||
self.kill_text(self.erase_between(self.cursor, before))
|
||||
self.kill_text(self.erase_between(self.input_state.cursor, before))
|
||||
}
|
||||
return num_killed
|
||||
}
|
||||
|
||||
func (self *Readline) ensure_position_in_bounds(pos *Position) *Position {
|
||||
pos.Y = utils.Max(0, utils.Min(pos.Y, len(self.lines)-1))
|
||||
line := self.lines[pos.Y]
|
||||
pos.Y = utils.Max(0, utils.Min(pos.Y, len(self.input_state.lines)-1))
|
||||
line := self.input_state.lines[pos.Y]
|
||||
pos.X = utils.Max(0, utils.Min(pos.X, len(line)))
|
||||
return pos
|
||||
}
|
||||
@ -451,24 +451,24 @@ func (self *Readline) yank(repeat_count uint, pop bool) bool {
|
||||
if text == "" {
|
||||
return false
|
||||
}
|
||||
before := self.cursor
|
||||
before := self.input_state.cursor
|
||||
if pop {
|
||||
self.ensure_position_in_bounds(&self.last_yank_extent.start)
|
||||
self.ensure_position_in_bounds(&self.last_yank_extent.end)
|
||||
self.erase_between(self.last_yank_extent.start, self.last_yank_extent.end)
|
||||
self.cursor = self.last_yank_extent.start
|
||||
before = self.cursor
|
||||
self.input_state.cursor = self.last_yank_extent.start
|
||||
before = self.input_state.cursor
|
||||
}
|
||||
self.add_text(text)
|
||||
self.last_yank_extent.start = before
|
||||
self.last_yank_extent.end = self.cursor
|
||||
self.last_yank_extent.end = self.input_state.cursor
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *Readline) apply_history_text(text string) {
|
||||
self.lines = utils.Splitlines(text)
|
||||
if len(self.lines) == 0 {
|
||||
self.lines = []string{""}
|
||||
self.input_state.lines = utils.Splitlines(text)
|
||||
if len(self.input_state.lines) == 0 {
|
||||
self.input_state.lines = []string{""}
|
||||
}
|
||||
}
|
||||
|
||||
@ -528,8 +528,14 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error {
|
||||
defer func() { self.last_action = ac }()
|
||||
switch ac {
|
||||
case ActionBackspace:
|
||||
if self.erase_chars_before_cursor(repeat_count, true) > 0 {
|
||||
return nil
|
||||
if self.history_search != nil {
|
||||
if self.remove_text_from_history_search(repeat_count) > 0 {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if self.erase_chars_before_cursor(repeat_count, true) > 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
case ActionDelete:
|
||||
if self.erase_chars_after_cursor(repeat_count, true) > 0 {
|
||||
@ -568,7 +574,7 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error {
|
||||
return nil
|
||||
}
|
||||
case ActionEndInput:
|
||||
line := self.lines[self.cursor.Y]
|
||||
line := self.input_state.lines[self.input_state.cursor.Y]
|
||||
if line == "" {
|
||||
return io.EOF
|
||||
}
|
||||
@ -672,6 +678,16 @@ func (self *Readline) perform_action(ac Action, repeat_count uint) error {
|
||||
self.add_text(text)
|
||||
}
|
||||
return nil
|
||||
case ActionTerminateHistorySearchAndRestore:
|
||||
if self.history_search != nil {
|
||||
self.end_history_search(false)
|
||||
return nil
|
||||
}
|
||||
case ActionTerminateHistorySearchAndApply:
|
||||
if self.history_search != nil {
|
||||
self.end_history_search(true)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return ErrCouldNotPerformAction
|
||||
}
|
||||
|
||||
@ -48,15 +48,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.X = 2
|
||||
rl.input_state.cursor.X = 2
|
||||
rl.add_text("12")
|
||||
}, "ab12", "cd", "ab12cd")
|
||||
dt("abcd", func(rl *Readline) {
|
||||
rl.cursor.X = 2
|
||||
rl.input_state.cursor.X = 2
|
||||
rl.add_text("12\n34")
|
||||
}, "ab12\n34", "cd", "ab12\n34cd")
|
||||
dt("abcd\nxyz", func(rl *Readline) {
|
||||
rl.cursor.X = 2
|
||||
rl.input_state.cursor.X = 2
|
||||
rl.add_text("12\n34")
|
||||
}, "abcd\nxy12\n34", "z", "abcd\nxy12\n34z")
|
||||
}
|
||||
@ -80,7 +80,7 @@ func TestGetScreenLines(t *testing.T) {
|
||||
actual[i] = *x
|
||||
}
|
||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
||||
t.Fatalf("Did not get expected screen lines for: %#v and cursor: %+v\n%s", rl.AllText(), rl.cursor, diff)
|
||||
t.Fatalf("Did not get expected screen lines for: %#v and cursor: %+v\n%s", rl.AllText(), rl.input_state.cursor, diff)
|
||||
}
|
||||
}
|
||||
tsl(ScreenLine{Prompt: p(true), CursorCell: 3})
|
||||
@ -105,19 +105,19 @@ func TestGetScreenLines(t *testing.T) {
|
||||
ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1},
|
||||
ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: 3, CursorTextPos: 3, Text: "XYZ"},
|
||||
)
|
||||
rl.cursor = Position{X: 2}
|
||||
rl.input_state.cursor = Position{X: 2}
|
||||
tsl(
|
||||
ScreenLine{Prompt: p(true), CursorCell: 5, Text: "123", CursorTextPos: 2, TextLengthInCells: 3},
|
||||
ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1},
|
||||
ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: -1, CursorTextPos: -1, Text: "XYZ"},
|
||||
)
|
||||
rl.cursor = Position{X: 2, Y: 1}
|
||||
rl.input_state.cursor = Position{X: 2, Y: 1}
|
||||
tsl(
|
||||
ScreenLine{Prompt: p(true), CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3},
|
||||
ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: 4, CursorTextPos: 2},
|
||||
ScreenLine{OffsetInParentLine: 8, ParentLineNumber: 1, TextLengthInCells: 3, CursorCell: -1, CursorTextPos: -1, Text: "XYZ"},
|
||||
)
|
||||
rl.cursor = Position{X: 8, Y: 1}
|
||||
rl.input_state.cursor = Position{X: 8, Y: 1}
|
||||
tsl(
|
||||
ScreenLine{Prompt: p(true), CursorCell: -1, Text: "123", CursorTextPos: -1, TextLengthInCells: 3},
|
||||
ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "456abcde", TextLengthInCells: 8, CursorCell: -1, CursorTextPos: -1},
|
||||
@ -125,7 +125,7 @@ func TestGetScreenLines(t *testing.T) {
|
||||
)
|
||||
rl.ResetText()
|
||||
rl.add_text("1234567\nabc")
|
||||
rl.cursor = Position{X: 7}
|
||||
rl.input_state.cursor = Position{X: 7}
|
||||
tsl(
|
||||
ScreenLine{Prompt: p(true), CursorCell: -1, Text: "1234567", CursorTextPos: -1, TextLengthInCells: 7},
|
||||
ScreenLine{ParentLineNumber: 1, Prompt: p(false), Text: "abc", CursorCell: 2, TextLengthInCells: 3, CursorTextPos: 0},
|
||||
@ -164,8 +164,8 @@ func TestCursorMovement(t *testing.T) {
|
||||
}, "one", "à")
|
||||
|
||||
right := func(rl *Readline, amt uint, moved_amt uint, traverse_line_breaks bool) {
|
||||
rl.cursor.Y = 0
|
||||
rl.cursor.X = 0
|
||||
rl.input_state.cursor.Y = 0
|
||||
rl.input_state.cursor.X = 0
|
||||
actual := rl.move_cursor_right(amt, traverse_line_breaks)
|
||||
if actual != moved_amt {
|
||||
t.Fatalf("Failed to move cursor by %d\nactual != expected: %d != %d", amt, actual, moved_amt)
|
||||
@ -196,7 +196,7 @@ func TestCursorMovement(t *testing.T) {
|
||||
if len(initials) > 0 {
|
||||
initial = initials[0]
|
||||
}
|
||||
rl.cursor = initial
|
||||
rl.input_state.cursor = initial
|
||||
actual := rl.move_cursor_vertically(amt)
|
||||
if actual != moved_amt {
|
||||
t.Fatalf("Failed to move cursor by %#v for: %#v \nactual != expected: %#v != %#v", amt, rl.AllText(), actual, moved_amt)
|
||||
@ -216,7 +216,7 @@ func TestCursorMovement(t *testing.T) {
|
||||
rl.add_text("o\u0300ne two three\nfour five")
|
||||
|
||||
wf := func(amt uint, expected_amt uint, text_before_cursor string) {
|
||||
pos := rl.cursor
|
||||
pos := rl.input_state.cursor
|
||||
actual_amt := rl.move_to_end_of_word(amt, true, has_word_chars)
|
||||
if actual_amt != expected_amt {
|
||||
t.Fatalf("Failed to move to word end, expected amt (%d) != actual amt (%d)", expected_amt, actual_amt)
|
||||
@ -225,20 +225,20 @@ func TestCursorMovement(t *testing.T) {
|
||||
t.Fatalf("Did not get expected text before cursor for: %#v and cursor: %+v\n%s", rl.AllText(), pos, diff)
|
||||
}
|
||||
}
|
||||
rl.cursor = Position{}
|
||||
rl.input_state.cursor = Position{}
|
||||
wf(1, 1, "òne")
|
||||
wf(1, 1, "òne two")
|
||||
wf(1, 1, "òne two three")
|
||||
wf(1, 1, "òne two three\nfour")
|
||||
wf(1, 1, "òne two three\nfour five")
|
||||
wf(1, 0, "òne two three\nfour five")
|
||||
rl.cursor = Position{}
|
||||
rl.input_state.cursor = Position{}
|
||||
wf(5, 5, "òne two three\nfour five")
|
||||
rl.cursor = Position{X: 5}
|
||||
rl.input_state.cursor = Position{X: 5}
|
||||
wf(1, 1, "òne two")
|
||||
|
||||
wb := func(amt uint, expected_amt uint, text_before_cursor string) {
|
||||
pos := rl.cursor
|
||||
pos := rl.input_state.cursor
|
||||
actual_amt := rl.move_to_start_of_word(amt, true, has_word_chars)
|
||||
if actual_amt != expected_amt {
|
||||
t.Fatalf("Failed to move to word end, expected amt (%d) != actual amt (%d)", expected_amt, actual_amt)
|
||||
@ -247,18 +247,18 @@ func TestCursorMovement(t *testing.T) {
|
||||
t.Fatalf("Did not get expected text before cursor for: %#v and cursor: %+v\n%s", rl.AllText(), pos, diff)
|
||||
}
|
||||
}
|
||||
rl.cursor = Position{X: 2}
|
||||
rl.input_state.cursor = Position{X: 2}
|
||||
wb(1, 1, "")
|
||||
rl.cursor = Position{X: 8, Y: 1}
|
||||
rl.input_state.cursor = Position{X: 8, Y: 1}
|
||||
wb(1, 1, "òne two three\nfour ")
|
||||
wb(1, 1, "òne two three\n")
|
||||
wb(1, 1, "òne two ")
|
||||
wb(1, 1, "òne ")
|
||||
wb(1, 1, "")
|
||||
wb(1, 0, "")
|
||||
rl.cursor = Position{X: 8, Y: 1}
|
||||
rl.input_state.cursor = Position{X: 8, Y: 1}
|
||||
wb(5, 5, "")
|
||||
rl.cursor = Position{X: 5}
|
||||
rl.input_state.cursor = Position{X: 5}
|
||||
wb(1, 1, "")
|
||||
|
||||
}
|
||||
@ -330,11 +330,11 @@ func TestEraseChars(t *testing.T) {
|
||||
backspace(rl, 2, 2, false)
|
||||
}, "one\nt", "")
|
||||
dt("one\ntwo", func(rl *Readline) {
|
||||
rl.cursor.X = 1
|
||||
rl.input_state.cursor.X = 1
|
||||
backspace(rl, 2, 1, false)
|
||||
}, "one\n", "wo")
|
||||
dt("one\ntwo", func(rl *Readline) {
|
||||
rl.cursor.X = 1
|
||||
rl.input_state.cursor.X = 1
|
||||
backspace(rl, 2, 2, true)
|
||||
}, "one", "wo")
|
||||
dt("a😀", func(rl *Readline) {
|
||||
@ -345,8 +345,8 @@ func TestEraseChars(t *testing.T) {
|
||||
}, "b", "")
|
||||
|
||||
del := func(rl *Readline, amt uint, erased_amt uint, traverse_line_breaks bool) {
|
||||
rl.cursor.Y = 0
|
||||
rl.cursor.X = 0
|
||||
rl.input_state.cursor.Y = 0
|
||||
rl.input_state.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)
|
||||
@ -366,19 +366,19 @@ func TestEraseChars(t *testing.T) {
|
||||
rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
|
||||
}, "oree", "")
|
||||
dt("one\ntwo\nthree", func(rl *Readline) {
|
||||
rl.cursor.X = 1
|
||||
rl.input_state.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.input_state.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.input_state.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.input_state.cursor = Position{X: 0, Y: 0}
|
||||
rl.erase_between(Position{X: 1}, Position{X: 2, Y: 2})
|
||||
}, "", "oree")
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ var _ = fmt.Print
|
||||
const ST = "\x1b\\"
|
||||
const PROMPT_MARK = "\x1b]133;"
|
||||
|
||||
type SyntaxHighlightFunction func(text string, x, y int) string
|
||||
|
||||
type RlInit struct {
|
||||
Prompt string
|
||||
HistoryPath string
|
||||
@ -24,6 +26,7 @@ type RlInit struct {
|
||||
ContinuationPrompt string
|
||||
EmptyContinuationPrompt bool
|
||||
DontMarkPrompts bool
|
||||
SyntaxHighlighter SyntaxHighlightFunction
|
||||
}
|
||||
|
||||
type Position struct {
|
||||
@ -35,6 +38,7 @@ func (self Position) Less(other Position) bool {
|
||||
return self.Y < other.Y || (self.Y == other.Y && self.X < other.X)
|
||||
}
|
||||
|
||||
// Actions {{{
|
||||
type Action uint
|
||||
|
||||
const (
|
||||
@ -61,6 +65,8 @@ const (
|
||||
ActionHistoryLast
|
||||
ActionHistoryIncrementalSearchBackwards
|
||||
ActionHistoryIncrementalSearchForwards
|
||||
ActionTerminateHistorySearchAndApply
|
||||
ActionTerminateHistorySearchAndRestore
|
||||
ActionClearScreen
|
||||
ActionAddText
|
||||
ActionAbortCurrentLine
|
||||
@ -89,6 +95,8 @@ const (
|
||||
ActionNumericArgumentDigitMinus
|
||||
)
|
||||
|
||||
// }}}
|
||||
|
||||
type kill_ring struct {
|
||||
items *list.List
|
||||
}
|
||||
@ -133,6 +141,28 @@ type Prompt struct {
|
||||
Length int
|
||||
}
|
||||
|
||||
type InputState struct {
|
||||
// Input lines
|
||||
lines []string
|
||||
// The cursor position in the text
|
||||
cursor Position
|
||||
}
|
||||
|
||||
func (self InputState) copy() InputState {
|
||||
ans := self
|
||||
l := make([]string, len(self.lines))
|
||||
copy(l, self.lines)
|
||||
ans.lines = l
|
||||
return ans
|
||||
}
|
||||
|
||||
type syntax_highlighted struct {
|
||||
lines []string
|
||||
src_for_last_highlight string
|
||||
highlighter SyntaxHighlightFunction
|
||||
last_highlighter_name string
|
||||
}
|
||||
|
||||
type Readline struct {
|
||||
prompt, continuation_prompt Prompt
|
||||
|
||||
@ -141,13 +171,10 @@ type Readline struct {
|
||||
history *History
|
||||
kill_ring kill_ring
|
||||
|
||||
input_state InputState
|
||||
// The number of lines after the initial line on the screen
|
||||
cursor_y int
|
||||
screen_width int
|
||||
// Input lines
|
||||
lines []string
|
||||
// The cursor position in the text
|
||||
cursor Position
|
||||
cursor_y int
|
||||
screen_width int
|
||||
last_yank_extent struct {
|
||||
start, end Position
|
||||
}
|
||||
@ -158,6 +185,7 @@ type Readline struct {
|
||||
keyboard_state KeyboardState
|
||||
fmt_ctx *markup.Context
|
||||
text_to_be_added string
|
||||
syntax_highlighted syntax_highlighted
|
||||
}
|
||||
|
||||
func (self *Readline) make_prompt(text string, is_secondary bool) Prompt {
|
||||
@ -178,7 +206,9 @@ func New(loop *loop.Loop, r RlInit) *Readline {
|
||||
}
|
||||
ans := &Readline{
|
||||
mark_prompts: !r.DontMarkPrompts, fmt_ctx: markup.New(true),
|
||||
loop: loop, lines: []string{""}, history: NewHistory(r.HistoryPath, hc), kill_ring: kill_ring{items: list.New().Init()},
|
||||
loop: loop, input_state: InputState{lines: []string{""}}, history: NewHistory(r.HistoryPath, hc),
|
||||
syntax_highlighted: syntax_highlighted{highlighter: r.SyntaxHighlighter},
|
||||
kill_ring: kill_ring{items: list.New().Init()},
|
||||
}
|
||||
ans.prompt = ans.make_prompt(r.Prompt, false)
|
||||
t := ""
|
||||
@ -201,11 +231,10 @@ func (self *Readline) AddHistoryItem(hi HistoryItem) {
|
||||
}
|
||||
|
||||
func (self *Readline) ResetText() {
|
||||
self.lines = []string{""}
|
||||
self.cursor = Position{}
|
||||
self.cursor_y = 0
|
||||
self.input_state = InputState{lines: []string{""}}
|
||||
self.last_action = ActionNil
|
||||
self.keyboard_state = KeyboardState{}
|
||||
self.history_search = nil
|
||||
}
|
||||
|
||||
func (self *Readline) ChangeLoopAndResetText(lp *loop.Loop) {
|
||||
@ -278,7 +307,7 @@ func (self *Readline) AllText() string {
|
||||
}
|
||||
|
||||
func (self *Readline) CursorAtEndOfLine() bool {
|
||||
return self.cursor.X >= len(self.lines[self.cursor.Y])
|
||||
return self.input_state.cursor.X >= len(self.input_state.lines[self.input_state.cursor.Y])
|
||||
}
|
||||
|
||||
func (self *Readline) OnResize(old_size loop.ScreenSize, new_size loop.ScreenSize) error {
|
||||
|
||||
@ -4,7 +4,9 @@ package readline
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"kitty/tools/utils"
|
||||
"kitty/tools/wcswidth"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
@ -36,7 +38,7 @@ func (self *Readline) format_arg_prompt(cna string) string {
|
||||
}
|
||||
|
||||
func (self *Readline) prompt_for_line_number(i int) Prompt {
|
||||
is_line_with_cursor := i == self.cursor.Y
|
||||
is_line_with_cursor := i == self.input_state.cursor.Y
|
||||
if is_line_with_cursor && self.keyboard_state.current_numeric_argument != "" {
|
||||
return self.make_prompt(self.format_arg_prompt(self.keyboard_state.current_numeric_argument), i > 0)
|
||||
}
|
||||
@ -49,17 +51,48 @@ func (self *Readline) prompt_for_line_number(i int) Prompt {
|
||||
return self.continuation_prompt
|
||||
}
|
||||
|
||||
func (self *Readline) apply_syntax_highlighting() (lines []string, cursor Position) {
|
||||
highlighter := self.syntax_highlighted.highlighter
|
||||
highlighter_name := "default"
|
||||
if self.history_search != nil {
|
||||
highlighter = self.history_search_highlighter
|
||||
highlighter_name = "## history ##"
|
||||
}
|
||||
if highlighter == nil {
|
||||
return self.input_state.lines, self.input_state.cursor
|
||||
}
|
||||
src := strings.Join(self.input_state.lines, "\n")
|
||||
if len(self.syntax_highlighted.lines) > 0 && self.syntax_highlighted.last_highlighter_name == highlighter_name && self.syntax_highlighted.src_for_last_highlight == src {
|
||||
lines = self.syntax_highlighted.lines
|
||||
} else {
|
||||
if src == "" {
|
||||
lines = []string{""}
|
||||
} else {
|
||||
text := highlighter(src, self.input_state.cursor.X, self.input_state.cursor.Y)
|
||||
lines = utils.Splitlines(text)
|
||||
for len(lines) < len(self.input_state.lines) {
|
||||
lines = append(lines, "syntax highlighter malfunctioned")
|
||||
}
|
||||
}
|
||||
}
|
||||
line := lines[self.input_state.cursor.Y]
|
||||
w := wcswidth.Stringwidth(self.input_state.lines[self.input_state.cursor.Y][:self.input_state.cursor.X])
|
||||
x := len(wcswidth.TruncateToVisualLength(line, w))
|
||||
return lines, Position{X: x, Y: self.input_state.cursor.Y}
|
||||
}
|
||||
|
||||
func (self *Readline) get_screen_lines() []*ScreenLine {
|
||||
if self.screen_width == 0 {
|
||||
self.update_current_screen_size()
|
||||
}
|
||||
ans := make([]*ScreenLine, 0, len(self.lines))
|
||||
lines, cursor := self.apply_syntax_highlighting()
|
||||
ans := make([]*ScreenLine, 0, len(lines))
|
||||
found_cursor := false
|
||||
cursor_at_start_of_next_line := false
|
||||
for i, line := range self.lines {
|
||||
for i, line := range lines {
|
||||
prompt := self.prompt_for_line_number(i)
|
||||
offset := 0
|
||||
has_cursor := i == self.cursor.Y
|
||||
has_cursor := i == cursor.Y
|
||||
for is_first := true; is_first || offset < len(line); is_first = false {
|
||||
l, width := wcswidth.TruncateToVisualLengthWithWidth(line[offset:], self.screen_width-prompt.Length)
|
||||
sl := ScreenLine{
|
||||
@ -73,12 +106,12 @@ func (self *Readline) get_screen_lines() []*ScreenLine {
|
||||
sl.CursorTextPos = 0
|
||||
}
|
||||
ans = append(ans, &sl)
|
||||
if has_cursor && !found_cursor && offset <= self.cursor.X && self.cursor.X <= offset+len(l) {
|
||||
if has_cursor && !found_cursor && offset <= cursor.X && cursor.X <= offset+len(l) {
|
||||
found_cursor = true
|
||||
ctpos := self.cursor.X - offset
|
||||
ctpos := cursor.X - offset
|
||||
ccell := prompt.Length + wcswidth.Stringwidth(l[:ctpos])
|
||||
if ccell >= self.screen_width {
|
||||
if offset+len(l) < len(line) || i < len(self.lines)-1 {
|
||||
if offset+len(l) < len(line) || i < len(lines)-1 {
|
||||
cursor_at_start_of_next_line = true
|
||||
} else {
|
||||
ans = append(ans, &ScreenLine{ParentLineNumber: i, OffsetInParentLine: len(line)})
|
||||
|
||||
@ -12,6 +12,8 @@ import (
|
||||
|
||||
"kitty/tools/utils"
|
||||
"kitty/tools/wcswidth"
|
||||
|
||||
"github.com/google/shlex"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
@ -31,13 +33,12 @@ type HistoryMatches struct {
|
||||
}
|
||||
|
||||
type HistorySearch struct {
|
||||
query string
|
||||
tokens []string
|
||||
items []*HistoryItem
|
||||
current_idx int
|
||||
backwards bool
|
||||
original_lines []string
|
||||
original_cursor Position
|
||||
query string
|
||||
tokens []string
|
||||
items []*HistoryItem
|
||||
current_idx int
|
||||
backwards bool
|
||||
original_input_state InputState
|
||||
}
|
||||
|
||||
type History struct {
|
||||
@ -211,31 +212,32 @@ func (self *HistoryMatches) next(num uint) (ans *HistoryItem) {
|
||||
}
|
||||
|
||||
func (self *Readline) create_history_search(backwards bool, num uint) {
|
||||
self.history_search = &HistorySearch{backwards: backwards, original_lines: self.lines, original_cursor: self.cursor}
|
||||
self.history_search = &HistorySearch{backwards: backwards, original_input_state: self.input_state.copy()}
|
||||
self.push_keyboard_map(history_search_shortcuts())
|
||||
self.markup_history_search()
|
||||
}
|
||||
|
||||
func (self *Readline) end_history_search(accept bool) {
|
||||
self.cursor = Position{}
|
||||
if accept && self.history_search.current_idx < len(self.history_search.items) {
|
||||
self.lines = utils.Splitlines(self.history_search.items[self.history_search.current_idx].Cmd)
|
||||
self.cursor.Y = len(self.lines) - 1
|
||||
self.cursor.X = len(self.lines[self.cursor.Y])
|
||||
self.input_state.lines = utils.Splitlines(self.history_search.items[self.history_search.current_idx].Cmd)
|
||||
self.input_state.cursor.Y = len(self.input_state.lines) - 1
|
||||
self.input_state.cursor.X = len(self.input_state.lines[self.input_state.cursor.Y])
|
||||
} else {
|
||||
self.lines = self.history_search.original_lines
|
||||
self.cursor = self.history_search.original_cursor
|
||||
self.input_state = self.history_search.original_input_state
|
||||
}
|
||||
self.cursor = *self.ensure_position_in_bounds(&self.cursor)
|
||||
self.input_state.cursor = *self.ensure_position_in_bounds(&self.input_state.cursor)
|
||||
self.pop_keyboard_map()
|
||||
self.history_search = nil
|
||||
}
|
||||
|
||||
func (self *Readline) markup_history_search() {
|
||||
if len(self.history_search.items) == 0 {
|
||||
if len(self.history_search.tokens) == 0 {
|
||||
self.lines = []string{""}
|
||||
self.input_state.lines = []string{""}
|
||||
} else {
|
||||
self.lines = []string{"No matches for: " + self.fmt_ctx.BrightRed(self.history_search.query)}
|
||||
self.input_state.lines = []string{"No matches for: " + self.history_search.query}
|
||||
}
|
||||
self.cursor = Position{X: wcswidth.Stringwidth(self.lines[0])}
|
||||
self.input_state.cursor = Position{X: wcswidth.Stringwidth(self.input_state.lines[0])}
|
||||
return
|
||||
}
|
||||
lines := utils.Splitlines(self.history_search.items[self.history_search.current_idx].Cmd)
|
||||
@ -243,7 +245,6 @@ func (self *Readline) markup_history_search() {
|
||||
for _, tok := range self.history_search.tokens {
|
||||
for i, line := range lines {
|
||||
if idx := strings.Index(line, tok); idx > -1 {
|
||||
lines[i] = line[:idx] + self.fmt_ctx.Green(tok) + line[idx+len(tok):]
|
||||
q := Position{Y: i, X: idx}
|
||||
if q.Less(cursor) {
|
||||
cursor = q
|
||||
@ -252,31 +253,64 @@ func (self *Readline) markup_history_search() {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.lines = lines
|
||||
self.cursor = *self.ensure_position_in_bounds(&cursor)
|
||||
self.input_state.lines = lines
|
||||
self.input_state.cursor = *self.ensure_position_in_bounds(&cursor)
|
||||
}
|
||||
|
||||
func (self *Readline) remove_text_from_history_search(num uint) uint {
|
||||
l := len(self.history_search.query)
|
||||
nl := utils.Max(0, l-int(num))
|
||||
self.history_search.query = self.history_search.query[:nl]
|
||||
num_removed := uint(l - nl)
|
||||
self.add_text_to_history_search("") // update the search results
|
||||
return num_removed
|
||||
}
|
||||
|
||||
func (self *Readline) history_search_highlighter(text string, x, y int) string {
|
||||
if len(self.history_search.items) == 0 {
|
||||
return text
|
||||
}
|
||||
lines := utils.Splitlines(text)
|
||||
for _, tok := range self.history_search.tokens {
|
||||
for i, line := range lines {
|
||||
if idx := strings.Index(line, tok); idx > -1 {
|
||||
lines[i] = line[:idx] + self.fmt_ctx.Green(tok) + line[idx+len(tok):]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func (self *Readline) add_text_to_history_search(text string) {
|
||||
self.history_search.query += text
|
||||
self.history_search.tokens = strings.Split(self.history_search.query, " ")
|
||||
tokens, err := shlex.Split(self.history_search.query)
|
||||
if err != nil {
|
||||
tokens = strings.Split(self.history_search.query, " ")
|
||||
}
|
||||
self.history_search.tokens = tokens
|
||||
var current_item *HistoryItem
|
||||
if len(self.history_search.items) > 0 {
|
||||
current_item = self.history_search.items[self.history_search.current_idx]
|
||||
}
|
||||
items := make([]*HistoryItem, len(self.history.items))
|
||||
for i, x := range self.history.items {
|
||||
items[i] = &x
|
||||
}
|
||||
for _, token := range self.history_search.tokens {
|
||||
matches := make([]*HistoryItem, 0, len(items))
|
||||
for _, item := range items {
|
||||
if strings.Contains(item.Cmd, token) {
|
||||
matches = append(matches, item)
|
||||
}
|
||||
if len(self.history_search.tokens) == 0 {
|
||||
self.history_search.items = []*HistoryItem{}
|
||||
} else {
|
||||
items := make([]*HistoryItem, len(self.history.items))
|
||||
for i, x := range self.history.items {
|
||||
items[i] = &x
|
||||
}
|
||||
items = matches
|
||||
for _, token := range self.history_search.tokens {
|
||||
matches := make([]*HistoryItem, 0, len(items))
|
||||
for _, item := range items {
|
||||
if strings.Contains(item.Cmd, token) {
|
||||
matches = append(matches, item)
|
||||
}
|
||||
}
|
||||
items = matches
|
||||
}
|
||||
self.history_search.items = items
|
||||
}
|
||||
self.history_search.items = items
|
||||
idx := -1
|
||||
for i, item := range self.history_search.items {
|
||||
if item == current_item {
|
||||
|
||||
@ -121,18 +121,69 @@ func default_shortcuts() *ShortcutMap {
|
||||
return _default_shortcuts
|
||||
}
|
||||
|
||||
func (self *Readline) action_for_key_event(event *loop.KeyEvent, shortcuts map[string]Action) Action {
|
||||
for sc, ac := range shortcuts {
|
||||
if event.MatchesPressOrRepeat(sc) {
|
||||
return ac
|
||||
}
|
||||
var _history_search_shortcuts *ShortcutMap
|
||||
|
||||
func history_search_shortcuts() *ShortcutMap {
|
||||
if _history_search_shortcuts == nil {
|
||||
sm := ShortcutMap{leaves: make(map[string]Action, 32), children: map[string]*ShortcutMap{}}
|
||||
sm.add(ActionBackspace, "backspace")
|
||||
sm.add(ActionBackspace, "ctrl+h")
|
||||
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "home")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+a")
|
||||
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "end")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+e")
|
||||
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+home")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+end")
|
||||
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "alt+f")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+right")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+left")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "alt+b")
|
||||
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "left")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+b")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "right")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+f")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "up")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "down")
|
||||
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+c")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "ctrl+g")
|
||||
sm.add(ActionTerminateHistorySearchAndRestore, "escape")
|
||||
|
||||
sm.add(ActionTerminateHistorySearchAndApply, "ctrl+d")
|
||||
sm.add(ActionTerminateHistorySearchAndApply, "enter")
|
||||
sm.add(ActionTerminateHistorySearchAndApply, "ctrl+j")
|
||||
|
||||
_history_search_shortcuts = &sm
|
||||
}
|
||||
return ActionNil
|
||||
return _history_search_shortcuts
|
||||
}
|
||||
|
||||
var ErrCouldNotPerformAction = errors.New("Could not perform the specified action")
|
||||
var ErrAcceptInput = errors.New("Accept input")
|
||||
|
||||
func (self *Readline) push_keyboard_map(m *ShortcutMap) {
|
||||
maps := self.keyboard_state.active_shortcut_maps
|
||||
self.keyboard_state = KeyboardState{}
|
||||
if maps == nil {
|
||||
maps = make([]*ShortcutMap, 0, 2)
|
||||
}
|
||||
self.keyboard_state.active_shortcut_maps = append(maps, m)
|
||||
}
|
||||
|
||||
func (self *Readline) pop_keyboard_map() {
|
||||
maps := self.keyboard_state.active_shortcut_maps
|
||||
self.keyboard_state = KeyboardState{}
|
||||
if len(maps) > 0 {
|
||||
maps = maps[:len(maps)-1]
|
||||
self.keyboard_state.active_shortcut_maps = maps
|
||||
}
|
||||
}
|
||||
|
||||
func (self *Readline) handle_numeric_arg(ac Action) {
|
||||
t := "-"
|
||||
num := int(ac - ActionNumericArgumentDigit0)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user