kitty/tools/tui/readline/actions.go
2023-03-14 20:24:21 +05:30

689 lines
18 KiB
Go

// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package readline
import (
"fmt"
"io"
"strings"
"unicode"
"kitty/tools/utils"
"kitty/tools/wcswidth"
)
var _ = fmt.Print
func (self *Readline) text_upto_cursor_pos() string {
buf := strings.Builder{}
buf.Grow(1024)
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)
buf.WriteString("\n")
}
}
return buf.String()
}
func (self *Readline) text_after_cursor_pos() string {
buf := strings.Builder{}
buf.Grow(1024)
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.input_state.cursor.Y {
buf.WriteString(line)
buf.WriteString("\n")
}
}
ans := buf.String()
if ans != "" {
ans = ans[:len(ans)-1]
}
return ans
}
func (self *Readline) all_text() string {
return strings.Join(self.input_state.lines, "\n")
}
func (self *Readline) set_text(text string) {
self.move_to_start()
self.erase_chars_after_cursor(123456789, true)
if text != "" {
self.add_text(text)
}
self.move_to_end()
}
func (self *Readline) add_text(text string) {
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.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.input_state.cursor.X = len(line)
self.input_state.cursor.Y += 1
}
cline := self.input_state.lines[self.input_state.cursor.Y]
before_first_line := cline[:self.input_state.cursor.X]
after_first_line := ""
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.input_state.cursor.X = len(line)
new_lines = append(new_lines, line)
}
}
if has_trailing_newline {
add_line_break("")
}
if after_first_line != "" {
if len(new_lines) == 0 {
new_lines = append(new_lines, "")
}
new_lines[len(new_lines)-1] += after_first_line
}
if len(lines_after) > 0 {
new_lines = append(new_lines, lines_after...)
}
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.input_state.cursor.X == 0 {
if !traverse_line_breaks || self.input_state.cursor.Y == 0 {
return amt_moved
}
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.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
}
func (self *Readline) move_cursor_right(amt uint, traverse_line_breaks bool) (amt_moved uint) {
for amt_moved < amt {
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.input_state.cursor.Y += 1
self.input_state.cursor.X = 0
amt_moved++
continue
}
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
}
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.input_state.cursor.Y = target_line.ParentLineNumber
tp := wcswidth.TruncateToVisualLength(target_line.Text, visual_distance_into_text)
self.input_state.cursor.X = target_line.OffsetInParentLine + len(tp)
}
}
func (self *Readline) move_cursor_vertically(amt int) (ans int) {
if self.screen_width == 0 {
self.update_current_screen_size()
}
screen_lines := self.get_screen_lines()
cursor_line_num := 0
for i, sl := range screen_lines {
if sl.CursorCell > -1 {
cursor_line_num = i
break
}
}
target_line_num := utils.Min(utils.Max(0, cursor_line_num+amt), len(screen_lines)-1)
ans = target_line_num - cursor_line_num
if ans != 0 {
self.move_cursor_to_target_line(screen_lines[cursor_line_num], screen_lines[target_line_num])
}
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.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.input_state.lines[self.input_state.cursor.Y]
if self.input_state.cursor.X >= len(line) {
return false
}
self.input_state.cursor.X = len(line)
return true
}
func (self *Readline) move_to_start() bool {
if self.input_state.cursor.Y == 0 && self.input_state.cursor.X == 0 {
return false
}
self.input_state.cursor.Y = 0
self.move_to_start_of_line()
return true
}
func (self *Readline) move_to_end() bool {
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.input_state.cursor.Y = len(self.input_state.lines) - 1
self.move_to_end_of_line()
return true
}
func (self *Readline) erase_between(start, end Position) string {
if end.Less(start) {
start, end = end, start
}
buf := strings.Builder{}
if start.Y == end.Y {
line := self.input_state.lines[start.Y]
buf.WriteString(line[start.X:end.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.input_state.cursor.X -= end.X - start.X
}
}
return buf.String()
}
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.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.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.input_state.cursor.X -= end.X - start.X
}
}
} else {
if i == self.input_state.cursor.Y {
self.input_state.cursor = start
}
buf.WriteString(line)
buf.WriteString("\n")
}
}
self.input_state.lines = lines
return buf.String()
}
func (self *Readline) erase_chars_before_cursor(amt uint, traverse_line_breaks bool) uint {
pos := self.input_state.cursor
num := self.move_cursor_left(amt, traverse_line_breaks)
if num == 0 {
return num
}
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.input_state.cursor
num := self.move_cursor_right(amt, traverse_line_breaks)
if num == 0 {
return num
}
self.erase_between(pos, self.input_state.cursor)
return num
}
func has_word_chars(text string) bool {
for _, ch := range text {
if unicode.IsLetter(ch) || unicode.IsDigit(ch) {
return true
}
}
return false
}
func (self *Readline) move_to_end_of_word(amt uint, traverse_line_breaks bool, is_part_of_word func(string) bool) (num_of_words_moved uint) {
if amt == 0 {
return 0
}
line := self.input_state.lines[self.input_state.cursor.Y]
in_word := false
ci := wcswidth.NewCellIterator(line[self.input_state.cursor.X:])
sz := 0
for ci.Forward() {
current_is_word_char := is_part_of_word(ci.Current())
plen := sz
sz += len(ci.Current())
if current_is_word_char {
in_word = true
} else if in_word {
self.input_state.cursor.X += plen
amt--
num_of_words_moved++
if amt == 0 {
return
}
in_word = false
}
}
if self.move_to_end_of_line() {
amt--
num_of_words_moved++
}
if amt > 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)
}
}
return
}
func (self *Readline) move_to_start_of_word(amt uint, traverse_line_breaks bool, is_part_of_word func(string) bool) (num_of_words_moved uint) {
if amt == 0 {
return 0
}
line := self.input_state.lines[self.input_state.cursor.Y]
in_word := false
ci := wcswidth.NewCellIterator(line[:self.input_state.cursor.X]).GotoEnd()
sz := 0
for ci.Backward() {
current_is_word_char := is_part_of_word(ci.Current())
plen := sz
sz += len(ci.Current())
if current_is_word_char {
in_word = true
} else if in_word {
self.input_state.cursor.X -= plen
amt--
num_of_words_moved++
if amt == 0 {
return
}
in_word = false
}
}
if self.move_to_start_of_line() {
amt--
num_of_words_moved++
}
if amt > 0 {
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)
}
}
return
}
func (self *Readline) kill_text(text string) {
if ActionStartKillActions < self.last_action && self.last_action < ActionEndKillActions {
self.kill_ring.append_to_existing_item(text)
} else {
self.kill_ring.add_new_item(text)
}
}
func (self *Readline) kill_to_end_of_line() bool {
line := self.input_state.lines[self.input_state.cursor.Y]
if self.input_state.cursor.X >= len(line) {
return false
}
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.input_state.lines[self.input_state.cursor.Y]
if self.input_state.cursor.X <= 0 {
return false
}
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.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.input_state.cursor))
}
return num_killed
}
func (self *Readline) kill_previous_word(amt uint, traverse_line_breaks bool) (num_killed uint) {
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.input_state.cursor, before))
}
return num_killed
}
func has_no_space_chars(text string) bool {
for _, r := range text {
if unicode.IsSpace(r) {
return false
}
}
return true
}
func (self *Readline) kill_previous_space_delimited_word(amt uint, traverse_line_breaks bool) (num_killed uint) {
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.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.input_state.lines)-1))
line := self.input_state.lines[pos.Y]
pos.X = utils.Max(0, utils.Min(pos.X, len(line)))
return pos
}
func (self *Readline) yank(repeat_count uint, pop bool) bool {
if pop && self.last_action != ActionYank && self.last_action != ActionPopYank {
return false
}
text := ""
if pop {
text = self.kill_ring.pop_yank()
} else {
text = self.kill_ring.yank()
}
if text == "" {
return false
}
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.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.input_state.cursor
return true
}
func (self *Readline) history_first() bool {
self.create_history_matches()
return self.history_matches.first(self)
}
func (self *Readline) history_last() bool {
self.create_history_matches()
return self.history_matches.last(self)
}
func (self *Readline) history_prev(repeat_count uint) bool {
self.create_history_matches()
return self.history_matches.previous(repeat_count, self)
}
func (self *Readline) history_next(repeat_count uint) bool {
self.create_history_matches()
return self.history_matches.next(repeat_count, self)
}
func (self *Readline) _perform_action(ac Action, repeat_count uint) (err error, dont_set_last_action bool) {
switch ac {
case ActionBackspace:
if self.history_search != nil {
if self.remove_text_from_history_search(repeat_count) > 0 {
return
}
} else {
if self.erase_chars_before_cursor(repeat_count, true) > 0 {
return
}
}
case ActionDelete:
if self.erase_chars_after_cursor(repeat_count, true) > 0 {
return
}
case ActionMoveToStartOfLine:
if self.move_to_start_of_line() {
return
}
case ActionMoveToEndOfLine:
if self.move_to_end_of_line() {
return
}
case ActionMoveToEndOfWord:
if self.move_to_end_of_word(repeat_count, true, has_word_chars) > 0 {
return
}
case ActionMoveToStartOfWord:
if self.move_to_start_of_word(repeat_count, true, has_word_chars) > 0 {
return
}
case ActionMoveToStartOfDocument:
if self.move_to_start() {
return
}
case ActionMoveToEndOfDocument:
if self.move_to_end() {
return
}
case ActionCursorLeft:
if self.move_cursor_left(repeat_count, true) > 0 {
return
}
case ActionCursorRight:
if self.move_cursor_right(repeat_count, true) > 0 {
return
}
case ActionEndInput:
line := self.input_state.lines[self.input_state.cursor.Y]
if line == "" {
err = io.EOF
} else {
err = self.perform_action(ActionAcceptInput, 1)
}
return
case ActionAcceptInput:
err = ErrAcceptInput
return
case ActionCursorUp:
if self.move_cursor_vertically(-int(repeat_count)) != 0 {
return
}
case ActionCursorDown:
if self.move_cursor_vertically(int(repeat_count)) != 0 {
return
}
case ActionHistoryPreviousOrCursorUp:
dont_set_last_action = true
if self.perform_action(ActionCursorUp, repeat_count) == ErrCouldNotPerformAction {
err = self.perform_action(ActionHistoryPrevious, repeat_count)
}
return
case ActionHistoryNextOrCursorDown:
dont_set_last_action = true
if self.perform_action(ActionCursorDown, repeat_count) == ErrCouldNotPerformAction {
err = self.perform_action(ActionHistoryNext, repeat_count)
}
return
case ActionHistoryFirst:
if self.history_first() {
return
}
case ActionHistoryPrevious:
if self.history_prev(repeat_count) {
return
}
case ActionHistoryNext:
if self.history_next(repeat_count) {
return
}
case ActionHistoryLast:
if self.history_last() {
return
}
case ActionClearScreen:
self.loop.StartAtomicUpdate()
self.loop.ClearScreen()
self.RedrawNonAtomic()
self.loop.EndAtomicUpdate()
return
case ActionKillToEndOfLine:
if self.kill_to_end_of_line() {
return
}
case ActionKillToStartOfLine:
if self.kill_to_start_of_line() {
return
}
case ActionKillNextWord:
if self.kill_next_word(repeat_count, true) > 0 {
return
}
case ActionKillPreviousWord:
if self.kill_previous_word(repeat_count, true) > 0 {
return
}
case ActionKillPreviousSpaceDelimitedWord:
if self.kill_previous_space_delimited_word(repeat_count, true) > 0 {
return
}
case ActionYank:
if self.yank(repeat_count, false) {
return
}
case ActionPopYank:
if self.yank(repeat_count, true) {
return
}
case ActionAbortCurrentLine:
self.loop.QueueWriteString("\r\n")
self.ResetText()
return
case ActionHistoryIncrementalSearchForwards:
if self.history_search == nil {
self.create_history_search(false, repeat_count)
return
}
if self.next_history_search(false, repeat_count) {
return
}
case ActionHistoryIncrementalSearchBackwards:
if self.history_search == nil {
self.create_history_search(true, repeat_count)
return
}
if self.next_history_search(true, repeat_count) {
return
}
case ActionAddText:
text := strings.Repeat(self.text_to_be_added, int(repeat_count))
self.text_to_be_added = ""
if self.history_search != nil {
self.add_text_to_history_search(text)
} else {
self.add_text(text)
}
return
case ActionTerminateHistorySearchAndRestore:
if self.history_search != nil {
self.end_history_search(false)
return
}
case ActionTerminateHistorySearchAndApply:
if self.history_search != nil {
self.end_history_search(true)
return
}
case ActionCompleteForward:
if self.complete(true, repeat_count) {
return
}
case ActionCompleteBackward:
if self.complete(false, repeat_count) {
return
}
}
err = ErrCouldNotPerformAction
return
}
func (self *Readline) perform_action(ac Action, repeat_count uint) error {
err, dont_set_last_action := self._perform_action(ac, repeat_count)
if err == nil && !dont_set_last_action {
self.last_action = ac
if self.completions.current.results != nil && ac != ActionCompleteForward && ac != ActionCompleteBackward {
self.completions.current = completion{}
}
}
return err
}