More work on shell

This commit is contained in:
Kovid Goyal 2022-10-07 10:42:26 +05:30
parent fd36435262
commit 9f2b2eac85
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 134 additions and 29 deletions

View File

@ -3,7 +3,13 @@
package at
import (
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/google/shlex"
"kitty/tools/cli"
"kitty/tools/cli/markup"
@ -17,17 +23,20 @@ var formatter *markup.Context
const prompt = "🐱 "
func shell_loop(kill_if_signaled bool) (int, error) {
var ErrExec = errors.New("Execute command")
func shell_loop(rl *readline.Readline, kill_if_signaled bool) (int, error) {
lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors)
if err != nil {
return 1, err
}
rl := readline.New(lp, readline.RlInit{Prompt: prompt})
rl.ChangeLoopAndResetText(lp)
lp.OnInitialize = func() (string, error) {
rl.Start()
return "\r\n", nil
return "", nil
}
lp.OnFinalize = func() string { rl.End(); return "" }
lp.OnResumeFromStop = func() error {
rl.Start()
@ -42,6 +51,17 @@ func shell_loop(kill_if_signaled bool) (int, error) {
lp.OnKeyEvent = func(event *loop.KeyEvent) error {
err := rl.OnKeyEvent(event)
if err != nil {
if err == io.EOF {
lp.Quit(0)
return nil
}
if err == readline.ErrAcceptInput {
if strings.HasSuffix(rl.TextBeforeCursor(), "\\") && strings.HasPrefix(rl.TextAfterCursor(), "\n") {
rl.OnText("\n", false, false)
return nil
}
return ErrExec
}
return err
}
if event.Handled {
@ -67,9 +87,39 @@ func shell_loop(kill_if_signaled bool) (int, error) {
return 0, nil
}
func exec_command(cmdline string) bool {
parsed_cmdline, err := shlex.Split(cmdline)
if err != nil {
fmt.Fprintln(os.Stderr, "Could not parse cmdline:", err)
return true
}
if len(parsed_cmdline) == 0 {
return true
}
switch parsed_cmdline[0] {
case "exit":
return false
}
return true
}
func shell_main(cmd *cli.Command, args []string) (int, error) {
formatter = markup.New(true)
fmt.Println("Welcome to the kitty shell!")
fmt.Println("Use", formatter.Green("help"), "for assistance or", formatter.Green("exit"), "to quit.")
return shell_loop(true)
rl := readline.New(nil, readline.RlInit{Prompt: prompt})
for {
rc, err := shell_loop(rl, true)
if err != nil {
if err == ErrExec {
cmdline := rl.AllText()
cmdline = strings.ReplaceAll(cmdline, "\\\n", "")
if !exec_command(cmdline) {
return 0, nil
}
continue
}
}
return rc, err
}
}

View File

@ -53,6 +53,10 @@ type Loop struct {
// the terminal on shutdown
OnInitialize func() (string, error)
// Called just before the loop shuts down. Any returned string is written to the terminal before
// shutdown
OnFinalize func() string
// Called when a key event happens
OnKeyEvent func(event *KeyEvent) error

View File

@ -4,6 +4,7 @@ package readline
import (
"fmt"
"io"
"strings"
"kitty/tools/utils"
@ -259,24 +260,52 @@ func (self *Readline) erase_chars_after_cursor(amt uint, traverse_line_breaks bo
return num
}
func (self *Readline) perform_action(ac Action, repeat_count uint) bool {
func (self *Readline) next_word_char_pos(traverse_line_breaks bool) int {
return 0
}
func (self *Readline) perform_action(ac Action, repeat_count uint) error {
switch ac {
case ActionBackspace:
return self.erase_chars_before_cursor(repeat_count, true) > 0
if self.erase_chars_before_cursor(repeat_count, true) > 0 {
return nil
}
case ActionDelete:
return self.erase_chars_after_cursor(repeat_count, true) > 0
if self.erase_chars_after_cursor(repeat_count, true) > 0 {
return nil
}
case ActionMoveToStartOfLine:
return self.move_to_start_of_line()
if self.move_to_start_of_line() {
return nil
}
case ActionMoveToEndOfLine:
return self.move_to_end_of_line()
if self.move_to_end_of_line() {
return nil
}
case ActionMoveToStartOfDocument:
return self.move_to_start()
if self.move_to_start() {
return nil
}
case ActionMoveToEndOfDocument:
return self.move_to_end()
if self.move_to_end() {
return nil
}
case ActionCursorLeft:
return self.move_cursor_left(repeat_count, true) > 0
if self.move_cursor_left(repeat_count, true) > 0 {
return nil
}
case ActionCursorRight:
return self.move_cursor_right(repeat_count, true) > 0
if self.move_cursor_right(repeat_count, true) > 0 {
return nil
}
case ActionEndInput:
line := self.lines[self.cursor.Y]
if line == "" {
return io.EOF
}
return self.perform_action(ActionAcceptInput, 1)
case ActionAcceptInput:
return ErrAcceptInput
}
return false
return ErrCouldNotPerformAction
}

View File

@ -42,6 +42,8 @@ const (
ActionMoveToEndOfDocument
ActionCursorLeft
ActionCursorRight
ActionEndInput
ActionAcceptInput
)
type Readline struct {
@ -78,12 +80,32 @@ func New(loop *loop.Loop, r RlInit) *Readline {
return ans
}
func (self *Readline) ChangeLoopAndResetText(lp *loop.Loop) {
self.loop = lp
self.lines = []string{""}
self.cursor = Position{}
self.cursor_y = 0
}
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()
@ -94,15 +116,6 @@ func (self *Readline) RedrawNonAtomic() {
self.redraw()
}
func (self *Readline) End() {
self.loop.EndBracketedPaste()
self.loop.QueueWriteString("\r\n")
self.loop.SetCursorShape(loop.BLOCK_CURSOR, true)
if self.mark_prompts {
self.loop.QueueWriteString(PROMPT_MARK + "C" + ST)
}
}
func (self *Readline) OnKeyEvent(event *loop.KeyEvent) error {
err := self.handle_key_event(event)
if err == ErrCouldNotPerformAction {
@ -117,6 +130,14 @@ func (self *Readline) OnText(text string, from_key_event bool, in_bracketed_past
return nil
}
func (self *Readline) PerformAction(ac Action, repeat_count uint) bool {
return self.perform_action(ac, repeat_count)
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()
}

View File

@ -15,7 +15,6 @@ var default_shortcuts = map[string]Action{
"backspace": ActionBackspace,
"ctrl+h": ActionBackspace,
"delete": ActionDelete,
"ctrl+d": ActionDelete,
"home": ActionMoveToStartOfLine,
"ctrl+a": ActionMoveToStartOfLine,
@ -30,6 +29,9 @@ var default_shortcuts = map[string]Action{
"ctrl+b": ActionCursorLeft,
"right": ActionCursorRight,
"ctrl+f": ActionCursorRight,
"ctrl+d": ActionEndInput,
"enter": ActionAcceptInput,
}
func action_for_key_event(event *loop.KeyEvent, shortcuts map[string]Action) Action {
@ -42,6 +44,7 @@ func action_for_key_event(event *loop.KeyEvent, shortcuts map[string]Action) Act
}
var ErrCouldNotPerformAction = errors.New("Could not perform the specified action")
var ErrAcceptInput = errors.New("Accept input")
func (self *Readline) handle_key_event(event *loop.KeyEvent) error {
if event.Text != "" {
@ -50,9 +53,7 @@ func (self *Readline) handle_key_event(event *loop.KeyEvent) error {
ac := action_for_key_event(event, default_shortcuts)
if ac != ActionNil {
event.Handled = true
if !self.perform_action(ac, 1) {
return ErrCouldNotPerformAction
}
return self.perform_action(ac, 1)
}
return nil
}