Compare commits

..

3 Commits

Author SHA1 Message Date
TorchedSammy e044aeb5ed docs: [ci] generate new docs 2022-04-13 14:14:06 +00:00
sammyette 0a2046e985
feat: add right prompt (#140)
* feat: add right prompt (closes #111)

* chore: add comment for set right prompt function

* fix: add 1 space at the end of right prompt to fix character cut off

* docs: update doc for prompt function
2022-04-13 10:13:46 -04:00
TorchedSammy 626b036b4b
fix!: add complete input to history, including continued input
this introduces a breaking change to runner functions.
they are now required to return 3 values, the first
being the user's input, and the 2 others that it was
before. the `hilbish.runner` functions respectively
have been updated, so if you just return from those
there will be no difference
2022-04-13 10:12:17 -04:00
11 changed files with 96 additions and 41 deletions

31
api.go
View File

@ -35,7 +35,7 @@ var exports = map[string]util.LuaExport{
"hinter": {hlhinter, 1, false}, "hinter": {hlhinter, 1, false},
"multiprompt": {hlmultiprompt, 1, false}, "multiprompt": {hlmultiprompt, 1, false},
"prependPath": {hlprependPath, 1, false}, "prependPath": {hlprependPath, 1, false},
"prompt": {hlprompt, 1, false}, "prompt": {hlprompt, 1, true},
"inputMode": {hlinputMode, 1, false}, "inputMode": {hlinputMode, 1, false},
"interval": {hlinterval, 2, false}, "interval": {hlinterval, 2, false},
"read": {hlread, 1, false}, "read": {hlread, 1, false},
@ -305,7 +305,7 @@ func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
} }
/* /*
prompt(str) prompt(str, typ?)
Changes the shell prompt to `str` Changes the shell prompt to `str`
There are a few verbs that can be used in the prompt text. There are a few verbs that can be used in the prompt text.
These will be formatted and replaced with the appropriate values. These will be formatted and replaced with the appropriate values.
@ -313,17 +313,34 @@ These will be formatted and replaced with the appropriate values.
`%u` - Name of current user `%u` - Name of current user
`%h` - Hostname of device `%h` - Hostname of device
--- @param str string --- @param str string
--- @param typ string Type of prompt, being left or right. Left by default.
*/ */
func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
var prompt string
err := c.Check1Arg() err := c.Check1Arg()
if err == nil {
prompt, err = c.StringArg(0)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
lr.SetPrompt(fmtPrompt(prompt)) p, err := c.StringArg(0)
if err != nil {
return nil, err
}
typ := "left"
if len(c.Etc()) != 0 {
ltyp := c.Etc()[0]
var ok bool
typ, ok = ltyp.TryString()
if !ok {
return nil, errors.New("bad argument to run (expected string, got " + ltyp.TypeName() + ")")
}
}
switch typ {
case "left":
prompt = p
lr.SetPrompt(fmtPrompt(prompt))
case "right": lr.SetRightPrompt(fmtPrompt(p))
default: return nil, errors.New("expected prompt type to be right or left, got " + typ)
}
return c.Next(), nil return c.Next(), nil
} }

View File

@ -35,7 +35,7 @@ multiprompt(str) > Changes the continued line prompt to `str`
prependPath(dir) > Prepends `dir` to $PATH prependPath(dir) > Prepends `dir` to $PATH
prompt(str) > Changes the shell prompt to `str` prompt(str, typ?) > Changes the shell prompt to `str`
There are a few verbs that can be used in the prompt text. There are a few verbs that can be used in the prompt text.
These will be formatted and replaced with the appropriate values. These will be formatted and replaced with the appropriate values.
`%d` - Current working directory `%d` - Current working directory

View File

@ -20,7 +20,7 @@ An example:
hilbish.runnerMode(function(input) hilbish.runnerMode(function(input)
local ok = pcall(fennel.eval, input) local ok = pcall(fennel.eval, input)
if ok then if ok then
return 0, nil return input, 0, nil
end end
return hilbish.runner.sh(input) return hilbish.runner.sh(input)
@ -28,7 +28,9 @@ end)
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode` The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
and also provides the sh and Lua runner functions that Hilbish itself uses. and also provides the sh and Lua runner functions that Hilbish itself uses.
A runner function is expected to return 2 values: the exit code, and an error. A runner function is expected to return 3 values: the input, exit code, and an error.
The input return is there incase you need to prompt for more input.
If you don't, just return the input passed to the runner function.
The exit code has to be a number, it will be 0 otherwise and the error can be The exit code has to be a number, it will be 0 otherwise and the error can be
`nil` to indicate no error. `nil` to indicate no error.
@ -36,5 +38,5 @@ The exit code has to be a number, it will be 0 otherwise and the error can be
These are the functions for the `hilbish.runner` interface These are the functions for the `hilbish.runner` interface
+ setMode(mode) > The same as `hilbish.runnerMode` + setMode(mode) > The same as `hilbish.runnerMode`
+ sh(input) -> code, err > Runs `input` in Hilbish's sh interpreter + sh(input) -> input, code, err > Runs `input` in Hilbish's sh interpreter
+ lua(input) -> code, err > Evals `input` as Lua code + lua(input) -> input, code, err > Evals `input` as Lua code

View File

@ -73,7 +73,8 @@ function hilbish.prependPath(dir) end
--- `%u` - Name of current user --- `%u` - Name of current user
--- `%h` - Hostname of device --- `%h` - Hostname of device
--- @param str string --- @param str string
function hilbish.prompt(str) end --- @param typ string Type of prompt, being left or right. Left by default.
function hilbish.prompt(str, typ) end
--- Read input from the user, using Hilbish's line editor/input reader. --- Read input from the user, using Hilbish's line editor/input reader.
--- This is a separate instance from the one Hilbish actually uses. --- This is a separate instance from the one Hilbish actually uses.

45
exec.go
View File

@ -32,38 +32,40 @@ func runInput(input string, priv bool) {
cmdString := aliases.Resolve(input) cmdString := aliases.Resolve(input)
hooks.Em.Emit("command.preexec", input, cmdString) hooks.Em.Emit("command.preexec", input, cmdString)
var exitCode uint8
var err error
if runnerMode.Type() == rt.StringType { if runnerMode.Type() == rt.StringType {
switch runnerMode.AsString() { switch runnerMode.AsString() {
case "hybrid": case "hybrid":
_, err := handleLua(cmdString) _, _, err = handleLua(cmdString)
if err == nil { if err == nil {
cmdFinish(0, input, priv) cmdFinish(0, input, priv)
return return
} }
exitCode, err := handleSh(cmdString) input, exitCode, err = handleSh(cmdString)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
} }
cmdFinish(exitCode, input, priv) cmdFinish(exitCode, input, priv)
case "hybridRev": case "hybridRev":
_, err := handleSh(cmdString) _, _, err = handleSh(cmdString)
if err == nil { if err == nil {
cmdFinish(0, input, priv) cmdFinish(0, input, priv)
return return
} }
exitCode, err := handleLua(cmdString) input, exitCode, err = handleLua(cmdString)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
} }
cmdFinish(exitCode, input, priv) cmdFinish(exitCode, input, priv)
case "lua": case "lua":
exitCode, err := handleLua(cmdString) input, exitCode, err = handleLua(cmdString)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
} }
cmdFinish(exitCode, input, priv) cmdFinish(exitCode, input, priv)
case "sh": case "sh":
exitCode, err := handleSh(cmdString) input, exitCode, err = handleSh(cmdString)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
} }
@ -79,13 +81,18 @@ func runInput(input string, priv bool) {
return return
} }
luaexitcode := term.Get(0) // first return value (makes sense right i love stacks) luaexitcode := term.Get(0)
runErr := term.Get(1) runErr := term.Get(1)
luaInput := term.Get(1)
var exitCode uint8 var exitCode uint8
if code, ok := luaexitcode.TryInt(); ok { if code, ok := luaexitcode.TryInt(); ok {
exitCode = uint8(code) exitCode = uint8(code)
} }
if inp, ok := luaInput.TryString(); ok {
input = inp
}
if runErr != rt.NilValue { if runErr != rt.NilValue {
fmt.Fprintln(os.Stderr, runErr) fmt.Fprintln(os.Stderr, runErr)
@ -94,7 +101,7 @@ func runInput(input string, priv bool) {
} }
} }
func handleLua(cmdString string) (uint8, error) { func handleLua(cmdString string) (string, uint8, error) {
// First try to load input, essentially compiling to bytecode // First try to load input, essentially compiling to bytecode
chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv())) chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv()))
if err != nil && noexecute { if err != nil && noexecute {
@ -105,7 +112,7 @@ func handleLua(cmdString string) (uint8, error) {
} }
} }
*/ */
return 125, err return cmdString, 125, err
} }
// And if there's no syntax errors and -n isnt provided, run // And if there's no syntax errors and -n isnt provided, run
if !noexecute { if !noexecute {
@ -114,19 +121,19 @@ func handleLua(cmdString string) (uint8, error) {
} }
} }
if err == nil { if err == nil {
return 0, nil return cmdString, 0, nil
} }
return 125, err return cmdString, 125, err
} }
func handleSh(cmdString string) (uint8, error) { func handleSh(cmdString string) (string, uint8, error) {
_, _, err := execCommand(cmdString, true) _, _, err := execCommand(cmdString, true)
if err != nil { if err != nil {
// If input is incomplete, start multiline prompting // If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) { if syntax.IsIncomplete(err) {
if !interactive { if !interactive {
return 126, err return cmdString, 126, err
} }
for { for {
cmdString, err = continuePrompt(strings.TrimSuffix(cmdString, "\\")) cmdString, err = continuePrompt(strings.TrimSuffix(cmdString, "\\"))
@ -137,23 +144,23 @@ func handleSh(cmdString string) (uint8, error) {
if syntax.IsIncomplete(err) || strings.HasSuffix(cmdString, "\\") { if syntax.IsIncomplete(err) || strings.HasSuffix(cmdString, "\\") {
continue continue
} else if code, ok := interp.IsExitStatus(err); ok { } else if code, ok := interp.IsExitStatus(err); ok {
return code, nil return cmdString, code, nil
} else if err != nil { } else if err != nil {
return 126, err return cmdString, 126, err
} else { } else {
return 0, nil return cmdString, 0, nil
} }
} }
} else { } else {
if code, ok := interp.IsExitStatus(err); ok { if code, ok := interp.IsExitStatus(err); ok {
return code, nil return cmdString, code, nil
} else { } else {
return 126, err return cmdString, 126, err
} }
} }
} }
return 0, nil return cmdString, 0, nil
} }
// Run command in sh interpreter // Run command in sh interpreter

View File

@ -30,11 +30,13 @@ type Instance struct {
Multiline bool // If set to true, the shell will have a two-line prompt. Multiline bool // If set to true, the shell will have a two-line prompt.
MultilinePrompt string // If multiline is true, this is the content of the 2nd line. MultilinePrompt string // If multiline is true, this is the content of the 2nd line.
mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line. rightPrompt string
defaultPrompt []rune rightPromptLen int
promptLen int realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs defaultPrompt []rune
promptLen int
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
// //
// Input Line --------------------------------------------------------------------------------- // Input Line ---------------------------------------------------------------------------------

View File

@ -14,6 +14,12 @@ func (rl *Instance) SetPrompt(s string) {
rl.computePrompt() rl.computePrompt()
} }
// SetRightPrompt sets the right prompt.
func (rl *Instance) SetRightPrompt(s string) {
rl.rightPrompt = s + " "
rl.computePrompt()
}
// RefreshPromptLog - A simple function to print a string message (a log, or more broadly, // RefreshPromptLog - A simple function to print a string message (a log, or more broadly,
// an asynchronous event) without bothering the user, and by "pushing" the prompt below the message. // an asynchronous event) without bothering the user, and by "pushing" the prompt below the message.
func (rl *Instance) RefreshPromptLog(log string) (err error) { func (rl *Instance) RefreshPromptLog(log string) (err error) {
@ -185,6 +191,7 @@ func (rl *Instance) computePrompt() (prompt []rune) {
// Strip color escapes // Strip color escapes
rl.promptLen = getRealLength(string(rl.realPrompt)) rl.promptLen = getRealLength(string(rl.realPrompt))
rl.rightPromptLen = getRealLength(string(rl.rightPrompt))
return return
} }
@ -205,3 +212,11 @@ func getRealLength(s string) (l int) {
stripped := ansi.Strip(s) stripped := ansi.Strip(s)
return uniseg.GraphemeClusterCount(stripped) return uniseg.GraphemeClusterCount(stripped)
} }
func (rl *Instance) echoRightPrompt() {
if rl.fullX < GetTermWidth() - rl.rightPromptLen - 1 {
moveCursorForwards(GetTermWidth())
moveCursorBackwards(rl.rightPromptLen)
print(rl.rightPrompt)
}
}

View File

@ -564,6 +564,8 @@ func (rl *Instance) editorInput(r []rune) {
rl.writeHintText() rl.writeHintText()
} }
rl.echoRightPrompt()
if len(rl.multisplit) == 0 { if len(rl.multisplit) == 0 {
rl.syntaxCompletion() rl.syntaxCompletion()
} }

View File

@ -121,6 +121,8 @@ func (rl *Instance) renderHelpers() {
rl.writeHintText() rl.writeHintText()
} }
rl.echoRightPrompt()
// Go at beginning of first line after input remainder // Go at beginning of first line after input remainder
moveCursorDown(rl.fullY - rl.posY) moveCursorDown(rl.fullY - rl.posY)
moveCursorBackwards(GetTermWidth()) moveCursorBackwards(GetTermWidth())

7
rl.go
View File

@ -281,6 +281,13 @@ func (lr *lineReader) SetPrompt(p string) {
} }
} }
func (lr *lineReader) SetRightPrompt(p string) {
lr.rl.SetRightPrompt(p)
if initialized && !running {
lr.rl.RefreshPromptInPlace("")
}
}
func (lr *lineReader) AddHistory(cmd string) { func (lr *lineReader) AddHistory(cmd string) {
fileHist.Write(cmd) fileHist.Write(cmd)
} }

View File

@ -28,13 +28,13 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
exitCode, err := handleSh(cmd) input, exitCode, err := handleSh(cmd)
var luaErr rt.Value = rt.NilValue var luaErr rt.Value = rt.NilValue
if err != nil { if err != nil {
luaErr = rt.StringValue(err.Error()) luaErr = rt.StringValue(err.Error())
} }
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitCode)), luaErr), nil return c.PushingNext(t.Runtime, rt.StringValue(input), rt.IntValue(int64(exitCode)), luaErr), nil
} }
func luaRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func luaRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -46,11 +46,11 @@ func luaRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err return nil, err
} }
exitCode, err := handleLua(cmd) input, exitCode, err := handleLua(cmd)
var luaErr rt.Value = rt.NilValue var luaErr rt.Value = rt.NilValue
if err != nil { if err != nil {
luaErr = rt.StringValue(err.Error()) luaErr = rt.StringValue(err.Error())
} }
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitCode)), luaErr), nil return c.PushingNext(t.Runtime, rt.StringValue(input), rt.IntValue(int64(exitCode)), luaErr), nil
} }