123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689 |
- // 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[: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[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 := min(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 = max(0, min(pos.Y, len(self.input_state.lines)-1))
- line := self.input_state.lines[pos.Y]
- pos.X = max(0, 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
- }
|