Compare commits

..

No commits in common. "1339dc4a2f2e1ecfca4289f799631ab2cd337769" and "e3c25586e42f770f85401440cb32cc6752fdfd39" have entirely different histories.

15 changed files with 72 additions and 354 deletions

View File

@ -27,7 +27,7 @@ jobs:
with:
go-version: '1.17.7'
- name: Build
run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} make
run: GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build
- uses: actions/upload-artifact@v2
if: matrix.goos == 'windows'
with:

View File

@ -46,8 +46,6 @@ includes git commit, branch, and (new!!) release name.
- Added `fg` and `bg` builtins
- `job.foreground()` and `job.background()`, when `job` is a job object,
foreground and backgrounds a job respectively.
- Friendlier functions to the `hilbish.runner` interface, which also allow
having and using multiple runners.
### Changed
- **Breaking Change:** Upgraded to Lua 5.4.
@ -58,9 +56,6 @@ This is probably one of (if not the) biggest things in this release.
user input, exit code, and error. User input has been added to the return to
account for runners wanting to prompt for continued input, and to add it
properly to history.
- **Breaking Change:** Job objects and timers are now Lua userdata instead
of a table, so their functions require you to call them with a colon instead
of a dot. (ie. `job.stop()` -> `job:stop()`)
- All `fs` module functions which take paths now implicitly expand ~ to home.
### Fixed

4
api.go
View File

@ -476,7 +476,7 @@ func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
timer := timers.create(timerTimeout, interval, cb)
timer.start()
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
return c.PushingNext1(t.Runtime, timer.lua()), nil
}
// interval(cb, time)
@ -502,7 +502,7 @@ func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
timer := timers.create(timerInterval, interval, cb)
timer.start()
return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
return c.PushingNext1(t.Runtime, timer.lua()), nil
}
// complete(scope, cb)

View File

@ -20,11 +20,8 @@ and `execPath` is an absolute path for the command executable.
- `disown(id)`: Removes a job by ID from the job table.
# Job Object
A job object is a piece of `userdata`. All the functions of a job require
you to call them with a colon, since they are *methods* for the job object.
Example: hilbish.jobs.last():foreground()
Which will foreground the last job.
A job object on the Lua side is a table with some functions.
On the under side it represents a job in the job table.
You can still have a job object for a disowned job,
it just won't be *working* anywhere. :^)

View File

@ -35,21 +35,8 @@ The exit code has to be a number, it will be 0 otherwise and the error can be
`nil` to indicate no error.
## Functions
These are the "low level" functions for the `hilbish.runner` interface.
These are the functions for the `hilbish.runner` interface
+ setMode(mode) > The same as `hilbish.runnerMode`
+ sh(input) -> input, code, err > Runs `input` in Hilbish's sh interpreter
+ lua(input) -> input, code, err > Evals `input` as Lua code
The others here are defined in Lua and have EmmyLua documentation.
These functions should be preferred over the previous ones.
+ setCurrent(mode) > The same as `setMode`, but works with runners managed
via the functions below.
+ add(name, runner) > Adds a runner to a table of available runners. The `runner`
argument is either a function or a table with a run callback.
+ set(name, runner) > The same as `add` but requires passing a table and
overwrites if the `name`d runner already exists.
+ get(name) > runner > Gets a runner by name. It is a table with at least a
run function, to run input.
+ exec(cmd, runnerName) > Runs `cmd` with a runner. If `runnerName` isn't passed,
the current runner mode is used.

View File

@ -16,17 +16,9 @@ a timer via ID.
when the timer is triggered.
# Timer Object
All those previously mentioned functions return a `timer` object, to which
you can stop and start a timer again.
An example of usage:
local t = hilbish.timers.create(1, 5000, function()
print 'hello!'
end)
t:stop()
print(t.running, t.duration, t.type)
t:start()
Those previously mentioned functions return a `timer` object, to which you can
stop and start a timer again. The functions of the timers interface also
return a timer object.
## Properties
- `duration`: amount of time the timer runs for in milliseconds

144
job.go
View File

@ -3,7 +3,6 @@ package main
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"os/exec"
@ -16,7 +15,6 @@ import (
)
var jobs *jobHandler
var jobMetaKey = rt.StringValue("hshjob")
type job struct {
cmd string
@ -34,7 +32,6 @@ type job struct {
cmderr io.Writer
stdout *bytes.Buffer
stderr *bytes.Buffer
ud *rt.UserData
}
func (j *job) start() error {
@ -67,7 +64,7 @@ func (j *job) start() error {
j.pid = proc.Pid
j.running = true
hooks.Em.Emit("job.start", rt.UserDataValue(j.ud))
hooks.Em.Emit("job.start", j.lua())
return err
}
@ -82,7 +79,7 @@ func (j *job) stop() {
func (j *job) finish() {
j.running = false
hooks.Em.Emit("job.done", rt.UserDataValue(j.ud))
hooks.Em.Emit("job.done", j.lua())
}
func (j *job) wait() {
@ -110,16 +107,28 @@ func (j *job) getProc() *os.Process {
return nil
}
func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
func (j *job) lua() rt.Value {
jobFuncs := map[string]util.LuaExport{
"stop": {j.luaStop, 0, false},
"start": {j.luaStart, 0, false},
"foreground": {j.luaForeground, 0, false},
"background": {j.luaBackground, 0, false},
}
luaJob := rt.NewTable()
util.SetExports(l, luaJob, jobFuncs)
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
luaJob.Set(rt.StringValue("cmd"), rt.StringValue(j.cmd))
luaJob.Set(rt.StringValue("running"), rt.BoolValue(j.running))
luaJob.Set(rt.StringValue("id"), rt.IntValue(int64(j.id)))
luaJob.Set(rt.StringValue("pid"), rt.IntValue(int64(j.pid)))
luaJob.Set(rt.StringValue("exitCode"), rt.IntValue(int64(j.exitCode)))
luaJob.Set(rt.StringValue("stdout"), rt.StringValue(string(j.stdout.Bytes())))
luaJob.Set(rt.StringValue("stderr"), rt.StringValue(string(j.stderr.Bytes())))
return rt.TableValue(luaJob)
}
func (j *job) luaStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if !j.running {
err := j.start()
exit := handleExecErr(err)
@ -130,16 +139,7 @@ func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if j.running {
j.stop()
j.finish()
@ -148,16 +148,7 @@ func luaStopJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if !j.running {
return nil, errors.New("job not running")
}
@ -166,7 +157,7 @@ func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jobs.foreground = true
// this is kinda funny
// background continues the process incase it got suspended
err = j.background()
err := j.background()
if err != nil {
return nil, err
}
@ -180,21 +171,12 @@ func luaForegroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func luaBackgroundJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
j, err := jobArg(c, 0)
if err != nil {
return nil, err
}
func (j *job) luaBackground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if !j.running {
return nil, errors.New("job not running")
}
err = j.background()
err := j.background()
if err != nil {
return nil, err
}
@ -233,10 +215,8 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job {
stdout: &bytes.Buffer{},
stderr: &bytes.Buffer{},
}
jb.ud = jobUserData(jb)
j.jobs[j.latestID] = jb
hooks.Em.Emit("job.add", rt.UserDataValue(jb.ud))
hooks.Em.Emit("job.add", jb.lua())
return jb
}
@ -277,44 +257,6 @@ func (j *jobHandler) stopAll() {
}
func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table {
jobMethods := rt.NewTable()
jFuncs := map[string]util.LuaExport{
"stop": {luaStopJob, 1, false},
"start": {luaStartJob, 1, false},
"foreground": {luaForegroundJob, 1, false},
"background": {luaBackgroundJob, 1, false},
}
util.SetExports(l, jobMethods, jFuncs)
jobMeta := rt.NewTable()
jobIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j, _ := jobArg(c, 0)
arg := c.Arg(1)
val := jobMethods.Get(arg)
if val != rt.NilValue {
return c.PushingNext1(t.Runtime, val), nil
}
keyStr, _ := arg.TryString()
switch keyStr {
case "cmd": val = rt.StringValue(j.cmd)
case "running": val = rt.BoolValue(j.running)
case "id": val = rt.IntValue(int64(j.id))
case "pid": val = rt.IntValue(int64(j.pid))
case "exitCode": val = rt.IntValue(int64(j.exitCode))
case "stdout": val = rt.StringValue(string(j.stdout.Bytes()))
case "stderr": val = rt.StringValue(string(j.stderr.Bytes()))
}
return c.PushingNext1(t.Runtime, val), nil
}
jobMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(jobIndex, "__index", 2, false)))
l.SetRegistry(jobMetaKey, rt.TableValue(jobMeta))
jobFuncs := map[string]util.LuaExport{
"all": {j.luaAllJobs, 0, false},
"last": {j.luaLastJob, 0, false},
@ -329,30 +271,6 @@ func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table {
return luaJob
}
func jobArg(c *rt.GoCont, arg int) (*job, error) {
j, ok := valueToJob(c.Arg(arg))
if !ok {
return nil, fmt.Errorf("#%d must be a job", arg + 1)
}
return j, nil
}
func valueToJob(val rt.Value) (*job, bool) {
u, ok := val.TryUserData()
if !ok {
return nil, false
}
j, ok := u.Value().(*job)
return j, ok
}
func jobUserData(j *job) *rt.UserData {
jobMeta := l.Registry(jobMetaKey)
return rt.NewUserData(j, jobMeta.AsTable())
}
func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j.mu.RLock()
defer j.mu.RUnlock()
@ -370,7 +288,7 @@ func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
return c.PushingNext(t.Runtime, rt.UserDataValue(job.ud)), nil
return c.PushingNext1(t.Runtime, job.lua()), nil
}
func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -399,7 +317,7 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jb := j.add(cmd, args, execPath)
return c.PushingNext1(t.Runtime, rt.UserDataValue(jb.ud)), nil
return c.PushingNext1(t.Runtime, jb.lua()), nil
}
func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -408,7 +326,7 @@ func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jobTbl := rt.NewTable()
for id, job := range j.jobs {
jobTbl.Set(rt.IntValue(int64(id)), rt.UserDataValue(job.ud))
jobTbl.Set(rt.IntValue(int64(id)), job.lua())
}
return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil
@ -440,5 +358,5 @@ func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
return c.PushingNext1(t.Runtime, rt.UserDataValue(job.ud)), nil
return c.PushingNext1(t.Runtime, job.lua()), nil
}

View File

@ -8,7 +8,6 @@ require 'nature.commands'
require 'nature.completions'
require 'nature.opts'
require 'nature.vim'
require 'nature.runner'
local shlvl = tonumber(os.getenv 'SHLVL')
if shlvl ~= nil then

View File

@ -1,106 +0,0 @@
local currentRunner = 'hybrid'
local runners = {}
-- lsp shut up
hilbish = hilbish
--- Get a runner by name.
--- @param name string
--- @return table
function hilbish.runner.get(name)
local r = runners[name]
if not r then
error(string.format('runner %s does not exist', name))
end
return r
end
--- Adds a runner to the table of available runners. If runner is a table,
--- it must have the run function in it.
--- @param name string
--- @param runner function | table
function hilbish.runner.add(name, runner)
if type(name) ~= 'string' then
error 'expected runner name to be a table'
end
if type(runner) == 'function' then
runner = {run = runner} -- this probably looks confusing
end
if type(runner) ~= 'table' then
error 'expected runner to be a table or function'
end
if runners[name] then
error(string.format('runner %s already exists', name))
end
hilbish.runner.set(name, runner)
end
--- Sets a runner by name. The runner table must have the run function in it.
--- @param name string
--- @param runner table
function hilbish.runner.set(name, runner)
if not runner.run or type(runner.run) ~= 'function' then
error 'run function in runner missing'
end
runners[name] = runner
end
--- Executes cmd with a runner. If runnerName isn't passed, it uses
--- the user's current runner.
--- @param cmd string
--- @param runnerName string?
--- @return string, number, string
function hilbish.runner.exec(cmd, runnerName)
if not runnerName then runnerName = currentRunner end
local r = hilbish.runner.get(runnerName)
return r.run(cmd)
end
--- Sets the current interactive/command line runner mode.
--- @param name string
function hilbish.runner.setCurrent(name)
local r = hilbish.runner.get(name)
currentRunner = name
hilbish.runner.setMode(r.run)
end
hilbish.runner.add('hybrid', function(input)
local cmdStr = hilbish.aliases.resolve(input)
local _, _, err = hilbish.runner.lua(cmdStr)
if not err then
return input, 0, nil
end
return hilbish.runner.sh(input)
end)
hilbish.runner.add('hybridRev', function(input)
local _, _, err = hilbish.runner.sh(input)
if not err then
return input, 0, nil
end
local cmdStr = hilbish.aliases.resolve(input)
return hilbish.runner.lua(cmdStr)
end)
hilbish.runner.add('lua', function(input)
local cmdStr = hilbish.aliases.resolve(input)
return hilbish.runner.lua(cmdStr)
end)
hilbish.runner.add('sh', function(input)
return hilbish.runner.sh(input)
end)

View File

@ -73,7 +73,6 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) {
// We perform filter right here, so we create a new completion group, and populate it with our results.
for i := range g.Suggestions {
if rl.regexSearch == nil { continue }
if rl.regexSearch.MatchString(g.Suggestions[i]) {
suggs = append(suggs, g.Suggestions[i])
} else if g.DisplayType == TabDisplayList && rl.regexSearch.MatchString(g.Descriptions[g.Suggestions[i]]) {

View File

@ -45,7 +45,6 @@ func (rl *Instance) echo() {
print(seqClearScreenBelow)
// Print the prompt
rl.promptInPlace("")
print(string(rl.realPrompt))
// Assemble the line, taking virtual completions into account

View File

@ -73,7 +73,8 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
return
}
func (rl *Instance) promptInPlace(prompt string) {
// RefreshPromptInPlace - Refreshes the prompt in the very same place he is.
func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
// We adjust cursor movement, depending on which mode we're currently in.
// Prompt data intependent
if !rl.modeTabCompletion {
@ -90,11 +91,15 @@ func (rl *Instance) promptInPlace(prompt string) {
rl.SetPrompt(prompt)
}
if rl.Multiline {
rl.tcUsedY += 1
}
// Clear the input line and everything below
print(seqClearLine)
moveCursorUp(rl.infoY + rl.tcUsedY)
moveCursorBackwards(GetTermWidth())
print("\r" + seqClearScreenBelow)
print("\r\n" + seqClearScreenBelow)
// Add a new line if needed
if rl.Multiline {
@ -103,11 +108,8 @@ func (rl *Instance) promptInPlace(prompt string) {
} else {
fmt.Print(rl.mainPrompt)
}
}
// RefreshPromptInPlace - Refreshes the prompt in the very same place he is.
func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
rl.promptInPlace(prompt)
// Refresh the line
rl.updateHelpers()
return

View File

@ -33,7 +33,6 @@ func (rl *Instance) updateTabFind(r []rune) {
var err error
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
if err != nil {
rl.RefreshPromptLog(err.Error())
rl.infoText = []rune(Red("Failed to match search regexp"))
}

View File

@ -6,6 +6,8 @@ import (
"os"
"time"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
)
@ -23,7 +25,6 @@ type timer struct{
fun *rt.Closure
th *timerHandler
ticker *time.Ticker
ud *rt.UserData
channel chan struct{}
}
@ -73,17 +74,8 @@ func (t *timer) stop() error {
return nil
}
func timerStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
t, err := timerArg(c, 0)
if err != nil {
return nil, err
}
err = t.start()
func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := t.start()
if err != nil {
return nil, err
}
@ -91,20 +83,26 @@ func timerStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil
}
func timerStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
t, err := timerArg(c, 0)
func (t *timer) luaStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := t.stop()
if err != nil {
return nil, err
}
err = t.stop()
if err != nil {
return nil, err
}
return c.Next(), nil
}
func (t *timer) lua() rt.Value {
tExports := map[string]util.LuaExport{
"start": {t.luaStart, 0, false},
"stop": {t.luaStop, 0, false},
}
luaTimer := rt.NewTable()
util.SetExports(l, luaTimer, tExports)
luaTimer.Set(rt.StringValue("type"), rt.IntValue(int64(t.typ)))
luaTimer.Set(rt.StringValue("running"), rt.BoolValue(t.running))
luaTimer.Set(rt.StringValue("duration"), rt.IntValue(int64(t.dur / time.Millisecond)))
return rt.TableValue(luaTimer)
}

View File

@ -1,7 +1,6 @@
package main
import (
"fmt"
"sync"
"time"
@ -11,8 +10,6 @@ import (
)
var timers *timerHandler
var timerMetaKey = rt.StringValue("hshtimer")
type timerHandler struct {
mu *sync.RWMutex
wg *sync.WaitGroup
@ -47,8 +44,6 @@ func (th *timerHandler) create(typ timerType, dur time.Duration, fun *rt.Closure
th: th,
id: th.latestID,
}
t.ud = timerUserData(t)
th.timers[th.latestID] = t
return t
@ -80,7 +75,7 @@ func (th *timerHandler) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
timerTyp := timerType(timerTypInt)
tmr := th.create(timerTyp, time.Duration(ms) * time.Millisecond, cb)
return c.PushingNext1(t.Runtime, rt.UserDataValue(tmr.ud)), nil
return c.PushingNext1(t.Runtime, tmr.lua()), nil
}
func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -94,45 +89,13 @@ func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
t := th.get(int(id))
if t != nil {
return c.PushingNext1(thr.Runtime, rt.UserDataValue(t.ud)), nil
return c.PushingNext1(thr.Runtime, t.lua()), nil
}
return c.Next(), nil
}
func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table {
timerMethods := rt.NewTable()
timerFuncs := map[string]util.LuaExport{
"start": {timerStart, 1, false},
"stop": {timerStop, 1, false},
}
util.SetExports(rtm, timerMethods, timerFuncs)
timerMeta := rt.NewTable()
timerIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
ti, _ := timerArg(c, 0)
arg := c.Arg(1)
val := timerMethods.Get(arg)
if val != rt.NilValue {
return c.PushingNext1(t.Runtime, val), nil
}
keyStr, _ := arg.TryString()
switch keyStr {
case "type": val = rt.IntValue(int64(ti.typ))
case "running": val = rt.BoolValue(ti.running)
case "duration": val = rt.IntValue(int64(ti.dur / time.Millisecond))
}
return c.PushingNext1(t.Runtime, val), nil
}
timerMeta.Set(rt.StringValue("__index"), rt.FunctionValue(rt.NewGoFunction(timerIndex, "__index", 2, false)))
l.SetRegistry(timerMetaKey, rt.TableValue(timerMeta))
thExports := map[string]util.LuaExport{
"create": {th.luaCreate, 3, false},
"get": {th.luaGet, 1, false},
@ -143,27 +106,3 @@ func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table {
return luaTh
}
func timerArg(c *rt.GoCont, arg int) (*timer, error) {
j, ok := valueToTimer(c.Arg(arg))
if !ok {
return nil, fmt.Errorf("#%d must be a timer", arg + 1)
}
return j, nil
}
func valueToTimer(val rt.Value) (*timer, bool) {
u, ok := val.TryUserData()
if !ok {
return nil, false
}
j, ok := u.Value().(*timer)
return j, ok
}
func timerUserData(j *timer) *rt.UserData {
timerMeta := l.Registry(timerMetaKey)
return rt.NewUserData(j, timerMeta.AsTable())
}