diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14e4d64..f9d4df8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
# 🎀 Changelog
+## Unreleased
+### Fixed
+- Heredocs having issues
+
+### Added
+- Adding `\` at the end of input will add a newline and prompt for more input.
+
## [2.3.2] - 2024-07-30
### Fixed
- Command path searching due to 2.3 changes to the shell interpreter
diff --git a/README.md b/README.md
index 4566c30..aed2dfc 100644
--- a/README.md
+++ b/README.md
@@ -13,19 +13,23 @@
Hilbish is an extensible shell designed to be highly customizable.
-It is configured in Lua and provides a good range of features.
-It aims to be easy to use for anyone but powerful enough for
-those who need it.
+
+It is configured in Lua, and provides a good range of features.
+It aims to be easy to use for anyone, and powerful enough for
+those who need more.
The motivation for choosing Lua was that its simpler and better to use
-than old shell script. It's fine for basic interactive shell uses,
-but that's the only place Hilbish has shell script; everything else is Lua
-and aims to be infinitely configurable. If something isn't, open an issue!
+than old shell scripts. It's fine for basic interactive shell uses,
+and supports [both Lua and Sh interactively](https://rosettea.github.io/Hilbish/docs/features/runner-mode/).
+
+That's the only place Hilbish can use traditional shell syntax though;
+everything else is Lua and aims to be infinitely configurable.
+
+If something isn't, open an issue!
# Screenshots
-
# Getting Hilbish
diff --git a/docs/api/hilbish/hilbish.runner.md b/docs/api/hilbish/hilbish.runner.md
index b5cfde4..8c89a52 100644
--- a/docs/api/hilbish/hilbish.runner.md
+++ b/docs/api/hilbish/hilbish.runner.md
@@ -21,16 +21,18 @@ A runner is passed the input and has to return a table with these values.
All are not required, only the useful ones the runner needs to return.
(So if there isn't an error, just omit `err`.)
-- `exitCode` (number): A numerical code to indicate the exit result.
-- `input` (string): The user input. This will be used to add
-to the history.
-- `err` (string): A string to indicate an interal error for the runner.
-It can be set to a few special values for Hilbish to throw the right hooks and have a better looking message:
-
-`[command]: not-found` will throw a command.not-found hook based on what `[command]` is.
-
-`[command]: not-executable` will throw a command.not-executable hook.
-- `continue` (boolean): Whether to prompt the user for more input.
+- `exitCode` (number): Exit code of the command
+- `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
+more is requested.
+- `err` (string): A string that represents an error from the runner.
+This should only be set when, for example, there is a syntax error.
+It can be set to a few special values for Hilbish to throw the right
+hooks and have a better looking message.
+ - `\: not-found` will throw a `command.not-found` hook
+ based on what `\` is.
+ - `\: not-executable` will throw a `command.not-executable` hook.
+- `continue` (boolean): Whether Hilbish should prompt the user for no input
+- `newline` (boolean): Whether a newline should be added at the end of `input`.
Here is a simple example of a fennel runner. It falls back to
shell script if fennel eval has an error.
diff --git a/docs/features/runner-mode.md b/docs/features/runner-mode.md
index 0f7a8dd..ec804c1 100644
--- a/docs/features/runner-mode.md
+++ b/docs/features/runner-mode.md
@@ -33,19 +33,6 @@ needs to run interactive input. For more detail, see the [API documentation](../
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
and also provides the shell script and Lua runner functions that Hilbish itself uses.
-A runner function is expected to return a table with the following values:
-- `exitCode` (number): Exit code of the command
-- `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
-more is requested.
-- `err` (string): A string that represents an error from the runner.
-This should only be set when, for example, there is a syntax error.
-It can be set to a few special values for Hilbish to throw the right
-hooks and have a better looking message.
- - `: not-found` will throw a `command.not-found` hook
- based on what `` is.
- - `: not-executable` will throw a `command.not-executable` hook.
-- `continue` (boolean): Whether Hilbish should prompt the user for no input
-
## Functions
These are the "low level" functions for the `hilbish.runner` interface.
diff --git a/exec.go b/exec.go
index f1c5a05..357c143 100644
--- a/exec.go
+++ b/exec.go
@@ -98,6 +98,7 @@ func runInput(input string, priv bool) {
var exitCode uint8
var err error
var cont bool
+ var newline bool
// save incase it changes while prompting (For some reason)
currentRunner := runnerMode
if currentRunner.Type() == rt.StringType {
@@ -108,9 +109,9 @@ func runInput(input string, priv bool) {
cmdFinish(0, input, priv)
return
}
- input, exitCode, cont, err = handleSh(input)
+ input, exitCode, cont, newline, err = handleSh(input)
case "hybridRev":
- _, _, _, err = handleSh(input)
+ _, _, _, _, err = handleSh(input)
if err == nil {
cmdFinish(0, input, priv)
return
@@ -119,12 +120,12 @@ func runInput(input string, priv bool) {
case "lua":
input, exitCode, err = handleLua(input)
case "sh":
- input, exitCode, cont, err = handleSh(input)
+ input, exitCode, cont, newline, err = handleSh(input)
}
} else {
// can only be a string or function so
var runnerErr error
- input, exitCode, cont, runnerErr, err = runLuaRunner(currentRunner, input)
+ input, exitCode, cont, newline, runnerErr, err = runLuaRunner(currentRunner, input)
if err != nil {
fmt.Fprintln(os.Stderr, err)
cmdFinish(124, input, priv)
@@ -137,15 +138,15 @@ func runInput(input string, priv bool) {
}
if cont {
- input, err = reprompt(input)
+ input, err = continuePrompt(input, newline)
if err == nil {
goto rerun
} else if err == io.EOF {
- return
+ lr.SetPrompt(fmtPrompt(prompt))
}
}
- if err != nil {
+ if err != nil && err != io.EOF {
if exErr, ok := isExecError(err); ok {
hooks.Emit("command." + exErr.typ, exErr.cmd)
} else {
@@ -155,26 +156,28 @@ func runInput(input string, priv bool) {
cmdFinish(exitCode, input, priv)
}
-func reprompt(input string) (string, error) {
+func reprompt(input string, newline bool) (string, error) {
for {
- in, err := continuePrompt(strings.TrimSuffix(input, "\\"))
+ /*
+ if strings.HasSuffix(input, "\\") {
+ input = strings.TrimSuffix(input, "\\") + "\n"
+ }
+ */
+ in, err := continuePrompt(input, newline)
if err != nil {
lr.SetPrompt(fmtPrompt(prompt))
return input, err
}
- if strings.HasSuffix(in, "\\") {
- continue
- }
return in, nil
}
}
-func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, runnerErr, err error) {
+func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8, continued bool, newline bool, runnerErr, err error) {
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 3, false)
err = rt.Call(l.MainThread(), runr, []rt.Value{rt.StringValue(userInput)}, term)
if err != nil {
- return "", 124, false, nil, err
+ return "", 124, false, false, nil, err
}
var runner *rt.Table
@@ -202,6 +205,10 @@ func runLuaRunner(runr rt.Value, userInput string) (input string, exitCode uint8
if c, ok := runner.Get(rt.StringValue("continue")).TryBool(); ok {
continued = c
}
+
+ if nl, ok := runner.Get(rt.StringValue("newline")).TryBool(); ok {
+ newline = nl
+ }
return
}
@@ -232,35 +239,40 @@ func handleLua(input string) (string, uint8, error) {
return cmdString, 125, err
}
-func handleSh(cmdString string) (input string, exitCode uint8, cont bool, runErr error) {
+func handleSh(cmdString string) (input string, exitCode uint8, cont bool, newline bool, runErr error) {
shRunner := hshMod.Get(rt.StringValue("runner")).AsTable().Get(rt.StringValue("sh"))
var err error
- input, exitCode, cont, runErr, err = runLuaRunner(shRunner, cmdString)
+ input, exitCode, cont, newline, runErr, err = runLuaRunner(shRunner, cmdString)
if err != nil {
runErr = err
}
return
}
-func execSh(cmdString string) (string, uint8, bool, error) {
+func execSh(cmdString string) (input string, exitcode uint8, cont bool, newline bool, e error) {
_, _, err := execCommand(cmdString, nil)
if err != nil {
// If input is incomplete, start multiline prompting
if syntax.IsIncomplete(err) {
if !interactive {
- return cmdString, 126, false, err
+ return cmdString, 126, false, false, err
}
- return cmdString, 126, true, err
+
+ newline := false
+ if strings.Contains(err.Error(), "unclosed here-document") {
+ newline = true
+ }
+ return cmdString, 126, true, newline, err
} else {
if code, ok := interp.IsExitStatus(err); ok {
- return cmdString, code, false, nil
+ return cmdString, code, false, false, nil
} else {
- return cmdString, 126, false, err
+ return cmdString, 126, false, false, err
}
}
}
- return cmdString, 0, false, nil
+ return cmdString, 0, false, false, nil
}
// Run command in sh interpreter
diff --git a/main.go b/main.go
index 41d1d35..1bddfc4 100644
--- a/main.go
+++ b/main.go
@@ -223,8 +223,9 @@ input:
}
if strings.HasSuffix(input, "\\") {
+ print("\n")
for {
- input, err = continuePrompt(input)
+ input, err = continuePrompt(strings.TrimSuffix(input, "\\") + "\n", false)
if err != nil {
running = true
lr.SetPrompt(fmtPrompt(prompt))
@@ -248,16 +249,24 @@ input:
exit(0)
}
-func continuePrompt(prev string) (string, error) {
+func continuePrompt(prev string, newline bool) (string, error) {
hooks.Emit("multiline", nil)
lr.SetPrompt(multilinePrompt)
+
cont, err := lr.Read()
if err != nil {
return "", err
}
- cont = strings.TrimSpace(cont)
- return prev + strings.TrimSuffix(cont, "\n"), nil
+ if newline {
+ cont = "\n" + cont
+ }
+
+ if strings.HasSuffix(cont, "\\") {
+ cont = strings.TrimSuffix(cont, "\\") + "\n"
+ }
+
+ return prev + cont, nil
}
// This semi cursed function formats our prompt (obviously)
diff --git a/runnermode.go b/runnermode.go
index 55adfdc..fb8bcf4 100644
--- a/runnermode.go
+++ b/runnermode.go
@@ -21,16 +21,18 @@ A runner is passed the input and has to return a table with these values.
All are not required, only the useful ones the runner needs to return.
(So if there isn't an error, just omit `err`.)
-- `exitCode` (number): A numerical code to indicate the exit result.
-- `input` (string): The user input. This will be used to add
-to the history.
-- `err` (string): A string to indicate an interal error for the runner.
-It can be set to a few special values for Hilbish to throw the right hooks and have a better looking message:
-
-`[command]: not-found` will throw a command.not-found hook based on what `[command]` is.
-
-`[command]: not-executable` will throw a command.not-executable hook.
-- `continue` (boolean): Whether to prompt the user for more input.
+- `exitCode` (number): Exit code of the command
+- `input` (string): The text input of the user. This is used by Hilbish to append extra input, in case
+more is requested.
+- `err` (string): A string that represents an error from the runner.
+This should only be set when, for example, there is a syntax error.
+It can be set to a few special values for Hilbish to throw the right
+hooks and have a better looking message.
+ - `: not-found` will throw a `command.not-found` hook
+ based on what `` is.
+ - `: not-executable` will throw a `command.not-executable` hook.
+- `continue` (boolean): Whether Hilbish should prompt the user for no input
+- `newline` (boolean): Whether a newline should be added at the end of `input`.
Here is a simple example of a fennel runner. It falls back to
shell script if fennel eval has an error.
@@ -85,7 +87,7 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return nil, err
}
- _, exitCode, cont, err := execSh(aliases.Resolve(cmd))
+ _, exitCode, cont, newline, err := execSh(aliases.Resolve(cmd))
var luaErr rt.Value = rt.NilValue
if err != nil {
luaErr = rt.StringValue(err.Error())
@@ -94,6 +96,7 @@ func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
runnerRet.Set(rt.StringValue("input"), rt.StringValue(cmd))
runnerRet.Set(rt.StringValue("exitCode"), rt.IntValue(int64(exitCode)))
runnerRet.Set(rt.StringValue("continue"), rt.BoolValue(cont))
+ runnerRet.Set(rt.StringValue("newline"), rt.BoolValue(newline))
runnerRet.Set(rt.StringValue("err"), luaErr)
return c.PushingNext(t.Runtime, rt.TableValue(runnerRet)), nil