More work on shell
This commit is contained in:
parent
fd36435262
commit
9f2b2eac85
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user