Compare commits

..

10 Commits

Author SHA1 Message Date
TorchedSammy 1b4b41846f
chore: merge from remote branch 2022-05-31 15:17:50 -04:00
TorchedSammy 3196f0dcb9
fix: don't refetch tab completion on refresh if its already open (closes #163) 2022-05-31 15:16:32 -04:00
TorchedSammy 3f745e7e4d
docs: update contributing info to include writing changes to changelog 2022-05-30 18:16:36 -04:00
TorchedSammy f9a2a981b4
fix: revert "fix: multiline cursor redraw"
This reverts commit 1339dc4a2f.
this caused a few issues, including
history search messing up previous lines
what this commit was trying to fix is unimportant anyway
2022-05-29 19:02:20 -04:00
TorchedSammy 1339dc4a2f
fix: multiline cursor redraw 2022-05-28 21:05:03 -04:00
TorchedSammy b4a5746093
ci: use make to build for artifacts 2022-05-28 20:21:57 -04:00
TorchedSammy 2faaf4e045
fix: print err don't panic and in the case of regex compile failing on menu find 2022-05-28 20:19:49 -04:00
sammyette 3990d370fa
feat: extend hilbish.runner interface to allow multiple runners (#159) 2022-05-28 19:06:18 -04:00
sammyette b28a2c24c1
refactor!: use userdata where appropriate (#157)
any interface which has lua side objects that are from go side for its api (namely jobs) they will use userdata instead of always creating a table to represent the object. this might or might not bring lower ram usage since there is now only 1 single reference to an object instead of always creating a table to expose on lua

breaking change since methods need to be called with a colon instead of dot
2022-05-28 19:03:44 -04:00
TorchedSammy e3c25586e4
fix: broken ansi escape codes on windows cmd 2022-05-28 09:07:28 -04:00
16 changed files with 362 additions and 67 deletions

View File

@ -1,5 +1,5 @@
--- ---
- [ ] I have reviewed CONTRIBUTING.md. - [ ] I have reviewed CONTRIBUTING.md.
- [ ] My commits and title use the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) format. - [ ] My commits and title use the [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/) format.
- [ ] I have documented any breaking changes according to [SemVer](https://semver.org/). - [ ] I have documented changes and additions in the CHANGELOG.md.
--- ---

View File

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

View File

@ -46,6 +46,8 @@ includes git commit, branch, and (new!!) release name.
- Added `fg` and `bg` builtins - Added `fg` and `bg` builtins
- `job.foreground()` and `job.background()`, when `job` is a job object, - `job.foreground()` and `job.background()`, when `job` is a job object,
foreground and backgrounds a job respectively. foreground and backgrounds a job respectively.
- Friendlier functions to the `hilbish.runner` interface, which also allow
having and using multiple runners.
### Changed ### Changed
- **Breaking Change:** Upgraded to Lua 5.4. - **Breaking Change:** Upgraded to Lua 5.4.
@ -56,6 +58,9 @@ 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 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 account for runners wanting to prompt for continued input, and to add it
properly to history. 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. - All `fs` module functions which take paths now implicitly expand ~ to home.
### Fixed ### Fixed

View File

@ -29,8 +29,9 @@ For any code contributions (Lua and/or Go), you should follow these rules:
1. Ensure that any new install or build dependencies are documented in 1. Ensure that any new install or build dependencies are documented in
the README.md and pull request. the README.md and pull request.
2. Mention any and all changes, this includes useful file locations and 2. Mention any and all changes, feature additons, removals, etc. This includes
breaking changes. useful file locations and breaking changes. Document them in the [changelog](CHANGELOG.md)
in the pull request.
3. We use [Semver](http://semver.org/) for versioning and 3. We use [Semver](http://semver.org/) for versioning and
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)

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 := timers.create(timerTimeout, interval, cb)
timer.start() timer.start()
return c.PushingNext1(t.Runtime, timer.lua()), nil return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
} }
// interval(cb, time) // 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 := timers.create(timerInterval, interval, cb)
timer.start() timer.start()
return c.PushingNext1(t.Runtime, timer.lua()), nil return c.PushingNext1(t.Runtime, rt.UserDataValue(timer.ud)), nil
} }
// complete(scope, cb) // complete(scope, cb)

View File

@ -20,8 +20,11 @@ and `execPath` is an absolute path for the command executable.
- `disown(id)`: Removes a job by ID from the job table. - `disown(id)`: Removes a job by ID from the job table.
# Job Object # Job Object
A job object on the Lua side is a table with some functions. A job object is a piece of `userdata`. All the functions of a job require
On the under side it represents a job in the job table. 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.
You can still have a job object for a disowned job, You can still have a job object for a disowned job,
it just won't be *working* anywhere. :^) it just won't be *working* anywhere. :^)

View File

@ -35,8 +35,21 @@ 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.
## Functions ## Functions
These are the functions for the `hilbish.runner` interface These are the "low level" functions for the `hilbish.runner` interface.
+ setMode(mode) > The same as `hilbish.runnerMode` + setMode(mode) > The same as `hilbish.runnerMode`
+ sh(input) -> input, code, err > Runs `input` in Hilbish's sh interpreter + sh(input) -> input, code, err > Runs `input` in Hilbish's sh interpreter
+ lua(input) -> input, code, err > Evals `input` as Lua code + 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,9 +16,17 @@ a timer via ID.
when the timer is triggered. when the timer is triggered.
# Timer Object # Timer Object
Those previously mentioned functions return a `timer` object, to which you can All those previously mentioned functions return a `timer` object, to which
stop and start a timer again. The functions of the timers interface also you can stop and start a timer again.
return a timer object.
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()
## Properties ## Properties
- `duration`: amount of time the timer runs for in milliseconds - `duration`: amount of time the timer runs for in milliseconds

11
init_windows.go 100644
View File

@ -0,0 +1,11 @@
// +build windows
package main
import "golang.org/x/sys/windows"
func init() {
var mode uint32
windows.GetConsoleMode(windows.Stdout, &mode)
windows.SetConsoleMode(windows.Stdout, mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
}

144
job.go
View File

@ -3,6 +3,7 @@ package main
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt"
"io" "io"
"os" "os"
"os/exec" "os/exec"
@ -15,6 +16,7 @@ import (
) )
var jobs *jobHandler var jobs *jobHandler
var jobMetaKey = rt.StringValue("hshjob")
type job struct { type job struct {
cmd string cmd string
@ -32,6 +34,7 @@ type job struct {
cmderr io.Writer cmderr io.Writer
stdout *bytes.Buffer stdout *bytes.Buffer
stderr *bytes.Buffer stderr *bytes.Buffer
ud *rt.UserData
} }
func (j *job) start() error { func (j *job) start() error {
@ -64,7 +67,7 @@ func (j *job) start() error {
j.pid = proc.Pid j.pid = proc.Pid
j.running = true j.running = true
hooks.Em.Emit("job.start", j.lua()) hooks.Em.Emit("job.start", rt.UserDataValue(j.ud))
return err return err
} }
@ -79,7 +82,7 @@ func (j *job) stop() {
func (j *job) finish() { func (j *job) finish() {
j.running = false j.running = false
hooks.Em.Emit("job.done", j.lua()) hooks.Em.Emit("job.done", rt.UserDataValue(j.ud))
} }
func (j *job) wait() { func (j *job) wait() {
@ -107,28 +110,16 @@ func (j *job) getProc() *os.Process {
return nil return nil
} }
func (j *job) lua() rt.Value { func luaStartJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jobFuncs := map[string]util.LuaExport{ if err := c.Check1Arg(); err != nil {
"stop": {j.luaStop, 0, false}, return nil, err
"start": {j.luaStart, 0, false},
"foreground": {j.luaForeground, 0, false},
"background": {j.luaBackground, 0, false},
} }
luaJob := rt.NewTable()
util.SetExports(l, luaJob, jobFuncs)
luaJob.Set(rt.StringValue("cmd"), rt.StringValue(j.cmd)) j, err := jobArg(c, 0)
luaJob.Set(rt.StringValue("running"), rt.BoolValue(j.running)) if err != nil {
luaJob.Set(rt.StringValue("id"), rt.IntValue(int64(j.id))) return nil, err
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 { if !j.running {
err := j.start() err := j.start()
exit := handleExecErr(err) exit := handleExecErr(err)
@ -139,7 +130,16 @@ func (j *job) luaStart(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 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
}
if j.running { if j.running {
j.stop() j.stop()
j.finish() j.finish()
@ -148,7 +148,16 @@ func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 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
}
if !j.running { if !j.running {
return nil, errors.New("job not running") return nil, errors.New("job not running")
} }
@ -157,7 +166,7 @@ func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jobs.foreground = true jobs.foreground = true
// this is kinda funny // this is kinda funny
// background continues the process incase it got suspended // background continues the process incase it got suspended
err := j.background() err = j.background()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -171,12 +180,21 @@ func (j *job) luaForeground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
func (j *job) luaBackground(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { 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
}
if !j.running { if !j.running {
return nil, errors.New("job not running") return nil, errors.New("job not running")
} }
err := j.background() err = j.background()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -215,8 +233,10 @@ func (j *jobHandler) add(cmd string, args []string, path string) *job {
stdout: &bytes.Buffer{}, stdout: &bytes.Buffer{},
stderr: &bytes.Buffer{}, stderr: &bytes.Buffer{},
} }
jb.ud = jobUserData(jb)
j.jobs[j.latestID] = jb j.jobs[j.latestID] = jb
hooks.Em.Emit("job.add", jb.lua()) hooks.Em.Emit("job.add", rt.UserDataValue(jb.ud))
return jb return jb
} }
@ -257,6 +277,44 @@ func (j *jobHandler) stopAll() {
} }
func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table { 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{ jobFuncs := map[string]util.LuaExport{
"all": {j.luaAllJobs, 0, false}, "all": {j.luaAllJobs, 0, false},
"last": {j.luaLastJob, 0, false}, "last": {j.luaLastJob, 0, false},
@ -271,6 +329,30 @@ func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table {
return luaJob 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) { func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
j.mu.RLock() j.mu.RLock()
defer j.mu.RUnlock() defer j.mu.RUnlock()
@ -288,7 +370,7 @@ func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
return c.PushingNext1(t.Runtime, job.lua()), nil return c.PushingNext(t.Runtime, rt.UserDataValue(job.ud)), nil
} }
func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -317,7 +399,7 @@ func (j *jobHandler) luaAddJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jb := j.add(cmd, args, execPath) jb := j.add(cmd, args, execPath)
return c.PushingNext1(t.Runtime, jb.lua()), nil return c.PushingNext1(t.Runtime, rt.UserDataValue(jb.ud)), nil
} }
func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -326,7 +408,7 @@ func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
jobTbl := rt.NewTable() jobTbl := rt.NewTable()
for id, job := range j.jobs { for id, job := range j.jobs {
jobTbl.Set(rt.IntValue(int64(id)), job.lua()) jobTbl.Set(rt.IntValue(int64(id)), rt.UserDataValue(job.ud))
} }
return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil
@ -358,5 +440,5 @@ func (j *jobHandler) luaLastJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
return c.PushingNext1(t.Runtime, job.lua()), nil return c.PushingNext1(t.Runtime, rt.UserDataValue(job.ud)), nil
} }

View File

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

106
nature/runner.lua 100644
View File

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

View File

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

View File

@ -6,8 +6,6 @@ import (
"os" "os"
"time" "time"
"hilbish/util"
rt "github.com/arnodel/golua/runtime" rt "github.com/arnodel/golua/runtime"
) )
@ -25,6 +23,7 @@ type timer struct{
fun *rt.Closure fun *rt.Closure
th *timerHandler th *timerHandler
ticker *time.Ticker ticker *time.Ticker
ud *rt.UserData
channel chan struct{} channel chan struct{}
} }
@ -74,8 +73,17 @@ func (t *timer) stop() error {
return nil return nil
} }
func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func timerStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := t.start() if err := c.Check1Arg(); err != nil {
return nil, err
}
t, err := timerArg(c, 0)
if err != nil {
return nil, err
}
err = t.start()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -83,26 +91,20 @@ func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil return c.Next(), nil
} }
func (t *timer) luaStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func timerStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
err := t.stop() if err := c.Check1Arg(); err != nil {
return nil, err
}
t, err := timerArg(c, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = t.stop()
if err != nil {
return nil, err
}
return c.Next(), nil 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,6 +1,7 @@
package main package main
import ( import (
"fmt"
"sync" "sync"
"time" "time"
@ -10,6 +11,8 @@ import (
) )
var timers *timerHandler var timers *timerHandler
var timerMetaKey = rt.StringValue("hshtimer")
type timerHandler struct { type timerHandler struct {
mu *sync.RWMutex mu *sync.RWMutex
wg *sync.WaitGroup wg *sync.WaitGroup
@ -44,6 +47,8 @@ func (th *timerHandler) create(typ timerType, dur time.Duration, fun *rt.Closure
th: th, th: th,
id: th.latestID, id: th.latestID,
} }
t.ud = timerUserData(t)
th.timers[th.latestID] = t th.timers[th.latestID] = t
return t return t
@ -75,7 +80,7 @@ func (th *timerHandler) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
timerTyp := timerType(timerTypInt) timerTyp := timerType(timerTypInt)
tmr := th.create(timerTyp, time.Duration(ms) * time.Millisecond, cb) tmr := th.create(timerTyp, time.Duration(ms) * time.Millisecond, cb)
return c.PushingNext1(t.Runtime, tmr.lua()), nil return c.PushingNext1(t.Runtime, rt.UserDataValue(tmr.ud)), nil
} }
func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) { func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
@ -89,13 +94,45 @@ func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
t := th.get(int(id)) t := th.get(int(id))
if t != nil { if t != nil {
return c.PushingNext1(thr.Runtime, t.lua()), nil return c.PushingNext1(thr.Runtime, rt.UserDataValue(t.ud)), nil
} }
return c.Next(), nil return c.Next(), nil
} }
func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table { 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{ thExports := map[string]util.LuaExport{
"create": {th.luaCreate, 3, false}, "create": {th.luaCreate, 3, false},
"get": {th.luaGet, 1, false}, "get": {th.luaGet, 1, false},
@ -106,3 +143,27 @@ func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table {
return luaTh 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())
}