More work on readline

This commit is contained in:
Kovid Goyal 2022-10-04 13:33:13 +05:30
parent 565526624f
commit c8296a44eb
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 197 additions and 1 deletions

View File

@ -8,6 +8,7 @@ import (
"kitty/tools/cli"
"kitty/tools/cli/markup"
"kitty/tools/tui/loop"
"kitty/tools/tui/readline"
)
var _ = fmt.Print
@ -21,11 +22,18 @@ func shell_loop(kill_if_signaled bool) (int, error) {
if err != nil {
return 1, err
}
rl := readline.New(lp, readline.RlInit{Prompt: prompt})
lp.OnInitialize = func() (string, error) {
lp.QueueWriteString(prompt)
rl.Start()
return "\r\n", nil
}
lp.OnResumeFromStop = func() error {
rl.Start()
return nil
}
err = lp.Run()
if err != nil {
return 1, err

View File

@ -193,6 +193,40 @@ func (self *Loop) Beep() {
self.QueueWriteString("\a")
}
func (self *Loop) StartAtomicUpdate() {
self.QueueWriteString(PENDING_UPDATE.EscapeCodeToSet())
}
func (self *Loop) EndAtomicUpdate() {
self.QueueWriteString(PENDING_UPDATE.EscapeCodeToReset())
}
func (self *Loop) SetCursorShape(shape CursorShapes, blink bool) {
self.QueueWriteString(CursorShape(shape, blink))
}
func (self *Loop) MoveCursorHorizontally(amt int) {
suffix := "C"
if amt < 0 {
suffix = "D"
amt *= -1
}
self.QueueWriteString(fmt.Sprintf("\x1b[%d%s", amt, suffix))
}
func (self *Loop) MoveCursorVertically(amt int) {
suffix := "B"
if amt < 0 {
suffix = "A"
amt *= -1
}
self.QueueWriteString(fmt.Sprintf("\x1b[%d%s", amt, suffix))
}
func (self *Loop) ClearToEndOfScreen() {
self.QueueWriteString("\x1b[J")
}
func (self *Loop) Quit(exit_code int) {
self.exit_code = exit_code
self.keep_going = false

View File

@ -21,6 +21,14 @@ const (
CLEAR_SCREEN = "\033[H\033[2J"
)
type CursorShapes uint
const (
BLOCK_CURSOR CursorShapes = 1
UNDERLINE_CURSOR CursorShapes = 3
BAR_CURSOR CursorShapes = 5
)
type Mode uint32
const private Mode = 1 << 31
@ -147,3 +155,10 @@ func (self *TerminalStateOptions) ResetStateEscapeCodes() string {
sb.WriteString(RESTORE_COLORS)
return sb.String()
}
func CursorShape(shape CursorShapes, blink bool) string {
if !blink {
shape += 1
}
return fmt.Sprintf("\x1b[%d q", shape)
}

81
tools/tui/readline/api.go Normal file
View File

@ -0,0 +1,81 @@
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package readline
import (
"fmt"
"kitty/tools/tui/loop"
"kitty/tools/wcswidth"
)
var _ = fmt.Print
const ST = "\x1b\\"
const PROMPT_MARK = "\x1b]133;"
type RlInit struct {
Prompt string
ContinuationPrompt string
EmptyContinuationPrompt bool
DontMarkPrompts bool
}
type Readline struct {
prompt string
prompt_len int
continuation_prompt string
continuation_prompt_len int
mark_prompts bool
loop *loop.Loop
// The number of lines after the initial line
cursor_y int
// Input lines
lines []string
// The line the cursor is at currently
cursor_line int
// The offset into the text of the cursor line
cursor_pos_in_line int
}
func New(loop *loop.Loop, r RlInit) *Readline {
ans := &Readline{
prompt: r.Prompt, prompt_len: wcswidth.Stringwidth(r.Prompt), mark_prompts: !r.DontMarkPrompts,
loop: loop, lines: []string{""},
}
if r.ContinuationPrompt != "" || !r.EmptyContinuationPrompt {
ans.continuation_prompt = r.ContinuationPrompt
if ans.continuation_prompt == "" {
ans.continuation_prompt = "> "
}
}
if ans.mark_prompts {
ans.prompt = PROMPT_MARK + "A" + ST + ans.prompt
ans.continuation_prompt = PROMPT_MARK + "A;k=s" + ST + ans.continuation_prompt
}
return ans
}
func (self *Readline) Start() {
self.loop.SetCursorShape(loop.BAR_CURSOR, true)
self.Redraw()
}
func (self *Readline) Redraw() {
self.loop.StartAtomicUpdate()
self.RedrawNonAtomic()
self.loop.EndAtomicUpdate()
}
func (self *Readline) RedrawNonAtomic() {
self.redraw()
}
func (self *Readline) End() {
self.loop.QueueWriteString("\r\n")
self.loop.SetCursorShape(loop.BLOCK_CURSOR, true)
if self.mark_prompts {
self.loop.QueueWriteString(PROMPT_MARK + "C" + ST)
}
}

View File

@ -0,0 +1,58 @@
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package readline
import (
"fmt"
"kitty/tools/wcswidth"
)
var _ = fmt.Print
func (self *Readline) write_line_with_prompt(line, prompt string, screen_width int) int {
self.loop.QueueWriteString(prompt)
self.loop.QueueWriteString(line)
w := wcswidth.Stringwidth(prompt) + wcswidth.Stringwidth(line)
return w / screen_width
}
func (self *Readline) move_cursor_to_text_position(pos, screen_width int) int {
num_of_lines := pos / screen_width
if num_of_lines > 0 {
self.loop.MoveCursorVertically(num_of_lines)
}
self.loop.QueueWriteString("\r")
x := pos % screen_width
self.loop.MoveCursorHorizontally(x)
return num_of_lines
}
func (self *Readline) redraw() {
if self.cursor_y > 0 {
self.loop.MoveCursorVertically(-self.cursor_y)
}
self.loop.QueueWriteString("\r")
self.loop.ClearToEndOfScreen()
y := 0
line_with_cursor := 0
screen_size, err := self.loop.ScreenSize()
if err != nil {
screen_size.WidthCells = 80
screen_size.HeightCells = 24
}
for i, line := range self.lines {
p := self.prompt
if i > 0 {
p = self.continuation_prompt
}
num_lines := self.write_line_with_prompt(line, p, int(screen_size.WidthCells))
if i == self.cursor_line {
line_with_cursor = y
}
y += num_lines
}
self.loop.MoveCursorVertically(-y + line_with_cursor)
line := self.lines[self.cursor_line]
line_with_cursor += self.move_cursor_to_text_position(wcswidth.Stringwidth(line[:self.cursor_pos_in_line]), int(screen_size.WidthCells))
self.cursor_y = line_with_cursor
}