123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285 |
- // License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
- package readline
- import (
- "container/list"
- "fmt"
- "strings"
- "kitty/tools/cli"
- "kitty/tools/cli/markup"
- "kitty/tools/tui/loop"
- "kitty/tools/wcswidth"
- )
- var _ = fmt.Print
- const ST = "\x1b\\"
- const PROMPT_MARK = "\x1b]133;"
- type SyntaxHighlightFunction = func(text string, x, y int) string
- type CompleterFunction = func(before_cursor, after_cursor string) *cli.Completions
- type RlInit struct {
- Prompt string
- HistoryPath string
- HistoryCount int
- ContinuationPrompt string
- EmptyContinuationPrompt bool
- DontMarkPrompts bool
- SyntaxHighlighter SyntaxHighlightFunction
- Completer CompleterFunction
- }
- 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 kill_ring struct {
- items *list.List
- }
- func (self *kill_ring) append_to_existing_item(text string) {
- e := self.items.Front()
- if e == nil {
- self.add_new_item(text)
- }
- e.Value = e.Value.(string) + text
- }
- func (self *kill_ring) add_new_item(text string) {
- if text != "" {
- self.items.PushFront(text)
- }
- }
- func (self *kill_ring) yank() string {
- e := self.items.Front()
- if e == nil {
- return ""
- }
- return e.Value.(string)
- }
- func (self *kill_ring) pop_yank() string {
- e := self.items.Front()
- if e == nil {
- return ""
- }
- self.items.MoveToBack(e)
- return self.yank()
- }
- func (self *kill_ring) clear() {
- self.items = self.items.Init()
- }
- type Prompt struct {
- Text string
- 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
- mark_prompts bool
- loop *loop.Loop
- 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, screen_height int
- last_yank_extent struct {
- start, end Position
- }
- bracketed_paste_buffer strings.Builder
- last_action Action
- history_matches *HistoryMatches
- history_search *HistorySearch
- keyboard_state KeyboardState
- fmt_ctx *markup.Context
- text_to_be_added string
- syntax_highlighted syntax_highlighted
- completions completions
- }
- func (self *Readline) make_prompt(text string, is_secondary bool) Prompt {
- if self.mark_prompts {
- m := PROMPT_MARK + "A"
- if is_secondary {
- m += ";k=s"
- }
- text = m + ST + text
- }
- return Prompt{Text: text, Length: wcswidth.Stringwidth(text)}
- }
- func New(loop *loop.Loop, r RlInit) *Readline {
- hc := r.HistoryCount
- if hc == 0 {
- hc = 8192
- }
- ans := &Readline{
- mark_prompts: !r.DontMarkPrompts, fmt_ctx: markup.New(true),
- loop: loop, input_state: InputState{lines: []string{""}}, history: NewHistory(r.HistoryPath, hc),
- syntax_highlighted: syntax_highlighted{highlighter: r.SyntaxHighlighter},
- completions: completions{completer: r.Completer},
- kill_ring: kill_ring{items: list.New().Init()},
- }
- if ans.completions.completer == nil && r.HistoryPath != "" {
- ans.completions.completer = ans.HistoryCompleter
- }
- ans.prompt = ans.make_prompt(r.Prompt, false)
- t := ""
- if r.ContinuationPrompt != "" || !r.EmptyContinuationPrompt {
- t = r.ContinuationPrompt
- if t == "" {
- t = ans.fmt_ctx.Yellow(">") + " "
- }
- }
- ans.continuation_prompt = ans.make_prompt(t, true)
- return ans
- }
- func (self *Readline) HistoryCompleter(before_cursor, after_cursor string) *cli.Completions {
- return self.history_completer(before_cursor, after_cursor)
- }
- func (self *Readline) SetPrompt(prompt string) {
- self.prompt = self.make_prompt(prompt, false)
- }
- func (self *Readline) Shutdown() {
- self.history.Shutdown()
- }
- func (self *Readline) AddHistoryItem(hi HistoryItem) {
- self.history.merge_items(hi)
- }
- func (self *Readline) ResetText() {
- self.input_state = InputState{lines: []string{""}}
- self.last_action = ActionNil
- self.keyboard_state = KeyboardState{}
- self.history_search = nil
- self.completions.current = completion{}
- self.cursor_y = 0
- }
- func (self *Readline) ChangeLoopAndResetText(lp *loop.Loop) {
- self.loop = lp
- self.ResetText()
- }
- func (self *Readline) Start() {
- self.loop.SetCursorShape(loop.BAR_CURSOR, true)
- self.loop.StartBracketedPaste()
- self.Redraw()
- }
- func (self *Readline) End() {
- self.loop.SetCursorShape(loop.BLOCK_CURSOR, true)
- self.loop.EndBracketedPaste()
- self.loop.QueueWriteString("\r\n")
- if self.mark_prompts {
- self.loop.QueueWriteString(PROMPT_MARK + "C" + ST)
- }
- }
- func MarkOutputStart() string {
- return PROMPT_MARK + "C" + ST
- }
- func (self *Readline) Redraw() {
- self.loop.StartAtomicUpdate()
- self.RedrawNonAtomic()
- self.loop.EndAtomicUpdate()
- }
- func (self *Readline) RedrawNonAtomic() {
- self.redraw()
- }
- func (self *Readline) OnKeyEvent(event *loop.KeyEvent) error {
- err := self.handle_key_event(event)
- if err == ErrCouldNotPerformAction {
- err = nil
- self.loop.Beep()
- }
- return err
- }
- func (self *Readline) OnText(text string, from_key_event bool, in_bracketed_paste bool) error {
- if in_bracketed_paste {
- self.bracketed_paste_buffer.WriteString(text)
- return nil
- }
- if self.bracketed_paste_buffer.Len() > 0 {
- self.bracketed_paste_buffer.WriteString(text)
- text = self.bracketed_paste_buffer.String()
- self.bracketed_paste_buffer.Reset()
- }
- self.text_to_be_added = text
- return self.dispatch_key_action(ActionAddText)
- }
- func (self *Readline) TextBeforeCursor() string {
- return self.text_upto_cursor_pos()
- }
- func (self *Readline) TextAfterCursor() string {
- return self.text_after_cursor_pos()
- }
- func (self *Readline) AllText() string {
- return self.all_text()
- }
- func (self *Readline) SetText(text string) {
- self.set_text(text)
- }
- func (self *Readline) MoveCursorToEnd() bool {
- return self.move_to_end()
- }
- func (self *Readline) CursorAtEndOfLine() bool {
- 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 {
- self.screen_width, self.screen_height = 0, 0
- self.Redraw()
- return nil
- }
|