2022-11-14 15:42:08 +05:30

266 lines
5.9 KiB
Go

// 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()},
}
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) Shutdown() {
self.history.Shutdown()
}
func (self *Readline) AddHistoryItem(hi HistoryItem) {
self.history.add_item(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) 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
}