More work on readline
This commit is contained in:
parent
565526624f
commit
c8296a44eb
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
81
tools/tui/readline/api.go
Normal 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)
|
||||
}
|
||||
}
|
||||
58
tools/tui/readline/draw.go
Normal file
58
tools/tui/readline/draw.go
Normal 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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user