mirror of https://github.com/Hilbis/Hilbish
Compare commits
100 Commits
7be96504b4
...
f360671e2a
Author | SHA1 | Date |
---|---|---|
TorchedSammy | f360671e2a | |
TorchedSammy | c6bb8bc663 | |
TorchedSammy | 1458ecdcab | |
ym555 | 6ccb9ebeff | |
Will Eccles | e34ab5314a | |
TorchedSammy | 0ae31123b9 | |
TorchedSammy | 7a17d7931f | |
TorchedSammy | d4084a82ba | |
TorchedSammy | 9d69b63a0f | |
TorchedSammy | b83c09a2b3 | |
TorchedSammy | bee8d6e9e6 | |
TorchedSammy | 48cb62282d | |
TorchedSammy | 1e48a3e03d | |
TorchedSammy | 4e8aa7ed1d | |
TorchedSammy | 919a52a630 | |
TorchedSammy | b0c950a96a | |
TorchedSammy | c4438579f6 | |
buffet | 59e81d3996 | |
Renzix | 7f161e6683 | |
TorchedSammy | ded0be275f | |
TorchedSammy | e3fdf84f5c | |
TorchedSammy | 194e4e01b7 | |
TorchedSammy | e5c9b85008 | |
TorchedSammy | e044aeb5ed | |
sammyette | 0a2046e985 | |
TorchedSammy | 626b036b4b | |
sammyette | ce625aca0c | |
TorchedSammy | 1715a1f626 | |
TorchedSammy | f002eca258 | |
TorchedSammy | 2814f44163 | |
TorchedSammy | ea7517be05 | |
TorchedSammy | 508fd5f8a2 | |
TorchedSammy | c95ff42dee | |
TorchedSammy | c342f4f6f5 | |
TorchedSammy | 393fe3962f | |
TorchedSammy | 8ae22127c0 | |
TorchedSammy | 8f942f6f60 | |
TorchedSammy | b712efd278 | |
TorchedSammy | ee4d97ff9a | |
TorchedSammy | 9ce861b080 | |
TorchedSammy | 69d38d7048 | |
sammyette | 0fc5f457ad | |
TorchedSammy | 64bf7024d2 | |
Renzix | 0ebd8d9035 | |
Renzix | 52caedc1f1 | |
TorchedSammy | 34ae8ade7b | |
TorchedSammy | 9ff6e5879f | |
TorchedSammy | 20fae8a870 | |
TorchedSammy | eff942433d | |
Renzix | 61c9e12a4a | |
TorchedSammy | 0aba60b5de | |
TorchedSammy | 62a6cc56b9 | |
TorchedSammy | e5d841a0a7 | |
TorchedSammy | 3e50e608c1 | |
TorchedSammy | 76f100ca77 | |
TorchedSammy | 0cad0e7e66 | |
TorchedSammy | 2fb481c4cb | |
TorchedSammy | 6ea25a22b3 | |
TorchedSammy | 577f00dfef | |
TorchedSammy | 722bd1cd80 | |
TorchedSammy | 1ba314d961 | |
sammyette | 84dce8c537 | |
TorchedSammy | 3345c51064 | |
TorchedSammy | a7e450904c | |
TorchedSammy | 23efc8e54d | |
TorchedSammy | 1d4c8a7645 | |
TorchedSammy | 7272e035d9 | |
TorchedSammy | 8a215ad742 | |
TorchedSammy | 6e69ee20f6 | |
TorchedSammy | bc15da2f1a | |
TorchedSammy | dd9e827735 | |
TorchedSammy | 3636efe7f8 | |
TorchedSammy | 053914ec45 | |
TorchedSammy | 1e884e7c89 | |
TorchedSammy | f27d60f827 | |
TorchedSammy | 754a63c74b | |
TorchedSammy | 2fe888e186 | |
TorchedSammy | 0d4143582f | |
TorchedSammy | 654ca4b527 | |
TorchedSammy | 802f444ba6 | |
TorchedSammy | 86a15e6363 | |
TorchedSammy | 96c1487bfa | |
TorchedSammy | 1e899bf18e | |
TorchedSammy | f03f8c0da1 | |
TorchedSammy | 1378a74e87 | |
TorchedSammy | 63bc398f1c | |
TorchedSammy | 579a0cd0ce | |
TorchedSammy | f433ab8a6f | |
TorchedSammy | eb0a81f7a2 | |
TorchedSammy | 24b88a0483 | |
TorchedSammy | f73c6d4aa8 | |
TorchedSammy | 925ded6cea | |
TorchedSammy | 92d0e195ab | |
TorchedSammy | 4da82e872c | |
TorchedSammy | b0ece71de3 | |
TorchedSammy | 8b5dc69950 | |
sammyette | 20a4cdb505 | |
TorchedSammy | 01d937afd8 | |
sammyette | 32b421d402 | |
TorchedSammy | 0ee47cc6f0 |
|
@ -12,16 +12,14 @@ jobs:
|
|||
matrix:
|
||||
goos: [linux, windows, darwin]
|
||||
goarch: ["386", amd64, arm64]
|
||||
exclude:
|
||||
exclude:
|
||||
- goarch: "386"
|
||||
goos: darwin
|
||||
goos: darwin
|
||||
- goarch: arm64
|
||||
goos: windows
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
|
|
42
CHANGELOG.md
42
CHANGELOG.md
|
@ -1,25 +1,56 @@
|
|||
# 🎀 Changelog
|
||||
|
||||
## [1.0.4] - 2021-03-12
|
||||
## [1.2.0] - 2022-03-17
|
||||
### Added
|
||||
- Job Management additions
|
||||
- `job.start` and `job.done` hooks (`doc hooks job`)
|
||||
- `hilbish.jobs` interface (`get(id)` function gets a job object via `id`, `all()` gets all)
|
||||
- Customizable runner/exec mode
|
||||
- However Hilbish runs interactive user input can now be changed Lua side (`doc runner-mode`)
|
||||
|
||||
### Changed
|
||||
- `vimMode` doc is now `vim-mode`
|
||||
|
||||
### Fixed
|
||||
- Make sure input which is supposed to go in history goes there
|
||||
- Cursor is right at the end of input on history search
|
||||
|
||||
## [1.1.0] - 2022-03-17
|
||||
### Added
|
||||
- `hilbish.vimAction` hook (`doc vimMode actions`)
|
||||
- `command.not-executable` hook (will replace `command.no-perm` in a future release)
|
||||
|
||||
### Fixed
|
||||
- Check if interactive before adding to history
|
||||
- Escape in vim mode exits all modes and not only insert
|
||||
- Make 2nd line in prompt empty if entire prompt is 1 line
|
||||
- Completion menu doesnt appear if there is only 1 result
|
||||
- Ignore SIGQUIT, which caused a panic unhandled
|
||||
- Remove hostname in greeting on Windows
|
||||
- Handle PATH binaries properly on Windows
|
||||
- Fix removal of dot in the beginning of folders/files that have them for file complete
|
||||
- Fix prompt being set to the continue prompt even when exited
|
||||
|
||||
## [1.0.4] - 2022-03-12
|
||||
### Fixed
|
||||
- Panic when history directory doesn't exist
|
||||
|
||||
## [1.0.3] - 2021-03-12
|
||||
## [1.0.3] - 2022-03-12
|
||||
### Fixed
|
||||
- Removed duplicate executable suggestions
|
||||
- User input is added to history now instead of what's ran by Hilbish
|
||||
- Formatting issue with prompt on no input
|
||||
|
||||
## [1.0.2] - 2021-03-06
|
||||
## [1.0.2] - 2022-03-06
|
||||
### Fixed
|
||||
- Cases where Hilbish's history directory doesn't exist will no longer cause a panic
|
||||
|
||||
## [1.0.1] - 2021-03-06
|
||||
## [1.0.1] - 2022-03-06
|
||||
### Fixed
|
||||
- Using `hilbish.appendPath` will no longer result in string spam (debugging thing left being)
|
||||
- Prompt gets set properly on startup
|
||||
|
||||
## [1.0.0] - 2021-03-06
|
||||
## [1.0.0] - 2022-03-06
|
||||
### Added
|
||||
- MacOS is now officialy supported, default compile time vars have been added
|
||||
for it
|
||||
|
@ -392,6 +423,7 @@ This input for example will prompt for more input to complete:
|
|||
|
||||
First "stable" release of Hilbish.
|
||||
|
||||
[1.1.0]: https://github.com/Rosettea/Hilbish/compare/v1.0.4...v1.1.0
|
||||
[1.0.4]: https://github.com/Rosettea/Hilbish/compare/v1.0.3...v1.0.4
|
||||
[1.0.3]: https://github.com/Rosettea/Hilbish/compare/v1.0.2...v1.0.3
|
||||
[1.0.2]: https://github.com/Rosettea/Hilbish/compare/v1.0.1...v1.0.2
|
||||
|
|
33
Makefile
33
Makefile
|
@ -1,31 +1,30 @@
|
|||
PREFIX ?= /usr
|
||||
DESTDIR ?=
|
||||
BINDIR ?= $(PREFIX)/bin
|
||||
LIBDIR ?= $(PREFIX)/share/hilbish
|
||||
|
||||
build:
|
||||
@go build -ldflags "-s -w"
|
||||
MY_GOFLAGS = -ldflags "-s -w"
|
||||
|
||||
dev:
|
||||
@go build -ldflags "-s -w -X main.version=$(shell git describe --tags)"
|
||||
all: dev
|
||||
|
||||
dev: MY_GOFLAGS = -ldflags "-s -w -X main.version=$(shell git describe --tags)"
|
||||
dev: build
|
||||
|
||||
build:
|
||||
go build $(MY_GOFLAGS)
|
||||
|
||||
install:
|
||||
@install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
|
||||
@mkdir -p "$(DESTDIR)$(LIBDIR)"
|
||||
@cp libs docs emmyLuaDocs prelude .hilbishrc.lua "$(DESTDIR)$(LIBDIR)" -r
|
||||
@grep "$(DESTDIR)$(BINDIR)/hilbish" -qxF /etc/shells || echo "$(DESTDIR)$(BINDIR)/hilbish" >> /etc/shells
|
||||
@echo "Hilbish Installed"
|
||||
install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
|
||||
mkdir -p "$(DESTDIR)$(LIBDIR)"
|
||||
cp -r libs docs emmyLuaDocs prelude .hilbishrc.lua "$(DESTDIR)$(LIBDIR)"
|
||||
grep -qxF "$(DESTDIR)$(BINDIR)/hilbish" /etc/shells || echo "$(DESTDIR)$(BINDIR)/hilbish" >> /etc/shells
|
||||
|
||||
uninstall:
|
||||
@rm -vrf \
|
||||
rm -vrf \
|
||||
"$(DESTDIR)$(BINDIR)/hilbish" \
|
||||
"$(DESTDIR)$(LIBDIR)"
|
||||
@sed -i '/hilbish/d' /etc/shells
|
||||
@echo "Hilbish Uninstalled"
|
||||
sed -i '/hilbish/d' /etc/shells
|
||||
|
||||
clean:
|
||||
@go clean
|
||||
go clean
|
||||
|
||||
all: build install
|
||||
|
||||
.PHONY: install uninstall build dev clean
|
||||
.PHONY: all dev build install uninstall clean
|
||||
|
|
51
README.md
51
README.md
|
@ -2,7 +2,7 @@
|
|||
<img src="./assets/hilbish-flower.png" width=128><br>
|
||||
<img src="./assets/hilbish-text.png" width=256><br>
|
||||
<blockquote>
|
||||
🌺 The flower shell. A comfy and nice little shell for Lua users and fans!
|
||||
🌺 The flower shell. A comfy and nice little shell for Lua fans!
|
||||
</blockquote>
|
||||
<p align="center">
|
||||
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/Rosettea/Hilbish?style=flat-square">
|
||||
|
@ -14,12 +14,25 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
Hilbish is a Unix-y shell which uses Lua for scripting. Things like the prompt,
|
||||
general configuration and such are done with Lua.
|
||||
Hilbish is a extensible shell (framework). It was made to be very customizable
|
||||
via the Lua programming language. It aims to be easy to use for the casual
|
||||
people but powerful for those who want to tinker more with their shell,
|
||||
the thing used to interface with most of the system.
|
||||
|
||||
For interactive use, it uses a library to run sh which works on all
|
||||
platforms Hilbish can be compiled for. It can also act as a Lua REPL if you want
|
||||
it to be.
|
||||
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!
|
||||
|
||||
# Table of Contents
|
||||
- [Screenshots](#Screenshots)
|
||||
- [Installation](#Installation)
|
||||
- [Prebuilt Bins](#Prebuilt-binaries)
|
||||
- [AUR](#AUR)
|
||||
- [Nixpkgs](#Nixpkgs)
|
||||
- [Manual Build](#Manual-Build)
|
||||
- [Getting Started](#Getting-Started)
|
||||
- [Contributing](#Contributing)
|
||||
|
||||
# Screenshots
|
||||
<div align="center">
|
||||
|
@ -29,8 +42,6 @@ it to be.
|
|||
</div>
|
||||
|
||||
# Installation
|
||||
**NOTE:** Hilbish is currently only officially supported and tested on Linux
|
||||
|
||||
## Prebuilt binaries
|
||||
Go [here](https://nightly.link/Rosettea/Hilbish/workflows/build/master) for
|
||||
builds on the master branch.
|
||||
|
@ -56,7 +67,7 @@ If you're new to nix you should probably read up on how to do that [here](https:
|
|||
### Prerequisites
|
||||
- [Go 1.17+](https://go.dev)
|
||||
|
||||
#### Build
|
||||
### Build
|
||||
First, clone Hilbish. The recursive is required, as some Lua libraries
|
||||
are submodules.
|
||||
```sh
|
||||
|
@ -78,13 +89,27 @@ make build
|
|||
|
||||
After you did all that, run `sudo make install` to install Hilbish globally.
|
||||
|
||||
# Getting Started
|
||||
At startup, you should see a message which says to run a `guide` command.
|
||||
This guide is a *very* simple and basic step through text of what Hilbish is
|
||||
and where to find documentation.
|
||||
|
||||
Documentation is primarily viewed via the in shell `doc` command.
|
||||
Autogenerated function docs and general docs about other things are included
|
||||
there, so be sure to read it.
|
||||
|
||||
Using Hilbish is the same as using any other Linux shell, with an addition
|
||||
that you can also run Lua. Hilbish can also act as an enhanced Lua REPL
|
||||
via `hilbish.runnerMode 'lua'`. To switch back to normal, use
|
||||
`hilbish.runnerMode 'hybrid'`.
|
||||
|
||||
# Contributing
|
||||
Any kind of contributions to Hilbish are welcome!
|
||||
Read [CONTRIBUTING.md](CONTRIBUTING.md) before getting started.
|
||||
Any kind of contributions are welcome! Hilbish is very easy to contribute to.
|
||||
Read [CONTRIBUTING.md](CONTRIBUTING.md) as a guideline to doing so.
|
||||
|
||||
**Thanks to everyone below who's contributed!**
|
||||
<a href="https://github.com/Hilbis/Hilbish/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Hilbis/Hilbish" />
|
||||
<a href="https://github.com/Rosettea/Hilbish/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=Rosettea/Hilbish" />
|
||||
</a>
|
||||
|
||||
*Made with [contributors-img](https://contrib.rocks).*
|
||||
|
|
93
aliases.go
93
aliases.go
|
@ -4,57 +4,59 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var aliases *hilbishAliases
|
||||
var aliases *aliasHandler
|
||||
|
||||
type hilbishAliases struct {
|
||||
type aliasHandler struct {
|
||||
aliases map[string]string
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
// initialize aliases map
|
||||
func NewAliases() *hilbishAliases {
|
||||
return &hilbishAliases{
|
||||
func newAliases() *aliasHandler {
|
||||
return &aliasHandler{
|
||||
aliases: make(map[string]string),
|
||||
mu: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hilbishAliases) Add(alias, cmd string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
func (a *aliasHandler) Add(alias, cmd string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
h.aliases[alias] = cmd
|
||||
a.aliases[alias] = cmd
|
||||
}
|
||||
|
||||
func (h *hilbishAliases) All() map[string]string {
|
||||
return h.aliases
|
||||
func (a *aliasHandler) All() map[string]string {
|
||||
return a.aliases
|
||||
}
|
||||
|
||||
func (h *hilbishAliases) Delete(alias string) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
func (a *aliasHandler) Delete(alias string) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
delete(h.aliases, alias)
|
||||
delete(a.aliases, alias)
|
||||
}
|
||||
|
||||
func (h *hilbishAliases) Resolve(cmdstr string) string {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
func (a *aliasHandler) Resolve(cmdstr string) string {
|
||||
a.mu.RLock()
|
||||
defer a.mu.RUnlock()
|
||||
|
||||
args := strings.Split(cmdstr, " ")
|
||||
for h.aliases[args[0]] != "" {
|
||||
alias := h.aliases[args[0]]
|
||||
for a.aliases[args[0]] != "" {
|
||||
alias := a.aliases[args[0]]
|
||||
cmdstr = alias + strings.TrimPrefix(cmdstr, args[0])
|
||||
cmdArgs, _ := splitInput(cmdstr)
|
||||
args = cmdArgs
|
||||
|
||||
if h.aliases[args[0]] == alias {
|
||||
if a.aliases[args[0]] == alias {
|
||||
break
|
||||
}
|
||||
if h.aliases[args[0]] != "" {
|
||||
if a.aliases[args[0]] != "" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
@ -64,41 +66,38 @@ func (h *hilbishAliases) Resolve(cmdstr string) string {
|
|||
|
||||
// lua section
|
||||
|
||||
func (h *hilbishAliases) Loader(L *lua.LState) *lua.LTable {
|
||||
func (a *aliasHandler) Loader(rtm *rt.Runtime) *rt.Table {
|
||||
// create a lua module with our functions
|
||||
hshaliasesLua := map[string]lua.LGFunction{
|
||||
"add": h.luaAdd,
|
||||
"list": h.luaList,
|
||||
"del": h.luaDelete,
|
||||
hshaliasesLua := map[string]util.LuaExport{
|
||||
"add": util.LuaExport{hlalias, 2, false},
|
||||
"list": util.LuaExport{a.luaList, 0, false},
|
||||
"del": util.LuaExport{a.luaDelete, 1, false},
|
||||
}
|
||||
|
||||
mod := L.SetFuncs(L.NewTable(), hshaliasesLua)
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, hshaliasesLua)
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func (h *hilbishAliases) luaAdd(L *lua.LState) int {
|
||||
alias := L.CheckString(1)
|
||||
cmd := L.CheckString(2)
|
||||
h.Add(alias, cmd)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func (h *hilbishAliases) luaList(L *lua.LState) int {
|
||||
aliasesList := L.NewTable()
|
||||
for k, v := range h.All() {
|
||||
aliasesList.RawSetString(k, lua.LString(v))
|
||||
func (a *aliasHandler) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
aliasesList := rt.NewTable()
|
||||
for k, v := range a.All() {
|
||||
aliasesList.Set(rt.StringValue(k), rt.StringValue(v))
|
||||
}
|
||||
|
||||
L.Push(aliasesList)
|
||||
|
||||
return 1
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(aliasesList)), nil
|
||||
}
|
||||
|
||||
func (h *hilbishAliases) luaDelete(L *lua.LState) int {
|
||||
alias := L.CheckString(1)
|
||||
h.Delete(alias)
|
||||
func (a *aliasHandler) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
alias, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.Delete(alias)
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
|
718
api.go
718
api.go
|
@ -4,6 +4,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -14,182 +16,128 @@ import (
|
|||
|
||||
"hilbish/util"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
"github.com/maxlandon/readline"
|
||||
"github.com/blackfireio/osinfo"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
|
||||
var exports = map[string]lua.LGFunction {
|
||||
"alias": hlalias,
|
||||
"appendPath": hlappendPath,
|
||||
"complete": hlcomplete,
|
||||
"cwd": hlcwd,
|
||||
"exec": hlexec,
|
||||
"goro": hlgoro,
|
||||
"multiprompt": hlmlprompt,
|
||||
"prependPath": hlprependPath,
|
||||
"prompt": hlprompt,
|
||||
"inputMode": hlinputMode,
|
||||
"interval": hlinterval,
|
||||
"read": hlread,
|
||||
"run": hlrun,
|
||||
"timeout": hltimeout,
|
||||
"which": hlwhich,
|
||||
var exports = map[string]util.LuaExport{
|
||||
"alias": {hlalias, 2, false},
|
||||
"appendPath": {hlappendPath, 1, false},
|
||||
"complete": {hlcomplete, 2, false},
|
||||
"cwd": {hlcwd, 0, false},
|
||||
"exec": {hlexec, 1, false},
|
||||
"runnerMode": {hlrunnerMode, 1, false},
|
||||
"goro": {hlgoro, 1, true},
|
||||
"highlighter": {hlhighlighter, 1, false},
|
||||
"hinter": {hlhinter, 1, false},
|
||||
"multiprompt": {hlmultiprompt, 1, false},
|
||||
"prependPath": {hlprependPath, 1, false},
|
||||
"prompt": {hlprompt, 1, true},
|
||||
"inputMode": {hlinputMode, 1, false},
|
||||
"interval": {hlinterval, 2, false},
|
||||
"read": {hlread, 1, false},
|
||||
"run": {hlrun, 1, true},
|
||||
"timeout": {hltimeout, 2, false},
|
||||
"which": {hlwhich, 1, false},
|
||||
}
|
||||
|
||||
var greeting string
|
||||
var hshMod *lua.LTable
|
||||
var hshMod *rt.Table
|
||||
var hilbishLoader = packagelib.Loader{
|
||||
Load: hilbishLoad,
|
||||
Name: "hilbish",
|
||||
}
|
||||
|
||||
func hilbishLoader(L *lua.LState) int {
|
||||
mod := L.SetFuncs(L.NewTable(), exports)
|
||||
func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
hshMod = mod
|
||||
|
||||
host, _ := os.Hostname()
|
||||
username := curuser.Username
|
||||
|
||||
greeting = `Welcome to {magenta}Hilbish{reset}, {cyan}` + curuser.Username + `{reset}.
|
||||
The nice lil shell for {blue}Lua{reset} fanatics!
|
||||
Check out the {blue}{bold}guide{reset} command to get started.
|
||||
`
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on windows
|
||||
}
|
||||
|
||||
util.SetField(L, mod, "ver", lua.LString(version), "Hilbish version")
|
||||
util.SetField(L, mod, "user", lua.LString(username), "Username of user")
|
||||
util.SetField(L, mod, "host", lua.LString(host), "Host name of the machine")
|
||||
util.SetField(L, mod, "home", lua.LString(curuser.HomeDir), "Home directory of the user")
|
||||
util.SetField(L, mod, "dataDir", lua.LString(dataDir), "Directory for Hilbish's data files")
|
||||
util.SetField(L, mod, "interactive", lua.LBool(interactive), "If this is an interactive shell")
|
||||
util.SetField(L, mod, "login", lua.LBool(interactive), "Whether this is a login shell")
|
||||
util.SetField(L, mod, "greeting", lua.LString(greeting), "Hilbish's welcome message for interactive shells. It has Lunacolors formatting.")
|
||||
util.SetField(l, mod, "vimMode", lua.LNil, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||
util.SetField(l, hshMod, "exitCode", lua.LNumber(0), "Exit code of last exected command")
|
||||
util.Document(L, mod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
|
||||
greeting = `Welcome to {magenta}Hilbish{reset}, {cyan}` + username + `{reset}.
|
||||
The nice lil shell for {blue}Lua{reset} fanatics!
|
||||
Check out the {blue}{bold}guide{reset} command to get started.
|
||||
`
|
||||
util.SetField(rtm, mod, "ver", rt.StringValue(version), "Hilbish version")
|
||||
util.SetField(rtm, mod, "user", rt.StringValue(username), "Username of user")
|
||||
util.SetField(rtm, mod, "host", rt.StringValue(host), "Host name of the machine")
|
||||
util.SetField(rtm, mod, "home", rt.StringValue(curuser.HomeDir), "Home directory of the user")
|
||||
util.SetField(rtm, mod, "dataDir", rt.StringValue(dataDir), "Directory for Hilbish's data files")
|
||||
util.SetField(rtm, mod, "interactive", rt.BoolValue(interactive), "If this is an interactive shell")
|
||||
util.SetField(rtm, mod, "login", rt.BoolValue(login), "Whether this is a login shell")
|
||||
util.SetField(rtm, mod, "greeting", rt.StringValue(greeting), "Hilbish's welcome message for interactive shells. It has Lunacolors formatting.")
|
||||
util.SetField(rtm, mod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||
util.SetField(rtm, hshMod, "exitCode", rt.IntValue(0), "Exit code of last exected command")
|
||||
util.Document(mod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
|
||||
|
||||
// hilbish.userDir table
|
||||
hshuser := L.NewTable()
|
||||
hshuser := rt.NewTable()
|
||||
|
||||
util.SetField(L, hshuser, "config", lua.LString(confDir), "User's config directory")
|
||||
util.SetField(L, hshuser, "data", lua.LString(userDataDir), "XDG data directory")
|
||||
util.Document(L, hshuser, "User directories to store configs and/or modules.")
|
||||
L.SetField(mod, "userDir", hshuser)
|
||||
util.SetField(rtm, hshuser, "config", rt.StringValue(confDir), "User's config directory")
|
||||
util.SetField(rtm, hshuser, "data", rt.StringValue(userDataDir), "XDG data directory")
|
||||
util.Document(hshuser, "User directories to store configs and/or modules.")
|
||||
mod.Set(rt.StringValue("userDir"), rt.TableValue(hshuser))
|
||||
|
||||
// hilbish.os table
|
||||
hshos := L.NewTable()
|
||||
hshos := rt.NewTable()
|
||||
info, _ := osinfo.GetOSInfo()
|
||||
|
||||
util.SetField(L, hshos, "family", lua.LString(info.Family), "Family name of the current OS")
|
||||
util.SetField(L, hshos, "name", lua.LString(info.Name), "Pretty name of the current OS")
|
||||
util.SetField(L, hshos, "version", lua.LString(info.Version), "Version of the current OS")
|
||||
util.Document(L, hshos, "OS info interface")
|
||||
L.SetField(mod, "os", hshos)
|
||||
util.SetField(rtm, hshos, "family", rt.StringValue(info.Family), "Family name of the current OS")
|
||||
util.SetField(rtm, hshos, "name", rt.StringValue(info.Name), "Pretty name of the current OS")
|
||||
util.SetField(rtm, hshos, "version", rt.StringValue(info.Version), "Version of the current OS")
|
||||
util.Document(hshos, "OS info interface")
|
||||
mod.Set(rt.StringValue("os"), rt.TableValue(hshos))
|
||||
|
||||
// hilbish.aliases table
|
||||
aliases = NewAliases()
|
||||
aliasesModule := aliases.Loader(L)
|
||||
util.Document(L, aliasesModule, "Alias inferface for Hilbish.")
|
||||
L.SetField(mod, "aliases", aliasesModule)
|
||||
aliases = newAliases()
|
||||
aliasesModule := aliases.Loader(rtm)
|
||||
util.Document(aliasesModule, "Alias inferface for Hilbish.")
|
||||
mod.Set(rt.StringValue("aliases"), rt.TableValue(aliasesModule))
|
||||
|
||||
// hilbish.history table
|
||||
historyModule := lr.Loader(L)
|
||||
util.Document(L, historyModule, "History interface for Hilbish.")
|
||||
L.SetField(mod, "history", historyModule)
|
||||
historyModule := lr.Loader(rtm)
|
||||
mod.Set(rt.StringValue("history"), rt.TableValue(historyModule))
|
||||
util.Document(historyModule, "History interface for Hilbish.")
|
||||
|
||||
// hilbish.completions table
|
||||
hshcomp := L.NewTable()
|
||||
// hilbish.completion table
|
||||
hshcomp := rt.NewTable()
|
||||
util.SetField(rtm, hshcomp, "files",
|
||||
rt.FunctionValue(rt.NewGoFunction(luaFileComplete, "files", 3, false)),
|
||||
"Completer for files")
|
||||
|
||||
util.SetField(L, hshcomp, "files", L.NewFunction(luaFileComplete), "Completer for files")
|
||||
util.SetField(L, hshcomp, "bins", L.NewFunction(luaBinaryComplete), "Completer for executables/binaries")
|
||||
util.Document(L, hshcomp, "Completions interface for Hilbish.")
|
||||
L.SetField(mod, "completion", hshcomp)
|
||||
util.SetField(rtm, hshcomp, "bins",
|
||||
rt.FunctionValue(rt.NewGoFunction(luaBinaryComplete, "bins", 3, false)),
|
||||
"Completer for executables/binaries")
|
||||
|
||||
L.Push(mod)
|
||||
util.Document(hshcomp, "Completions interface for Hilbish.")
|
||||
mod.Set(rt.StringValue("completion"), rt.TableValue(hshcomp))
|
||||
|
||||
return 1
|
||||
}
|
||||
// hilbish.runner table
|
||||
runnerModule := runnerModeLoader(rtm)
|
||||
util.Document(runnerModule, "Runner/exec interface for Hilbish.")
|
||||
mod.Set(rt.StringValue("runner"), rt.TableValue(runnerModule))
|
||||
|
||||
func luaFileComplete(L *lua.LState) int {
|
||||
query := L.CheckString(1)
|
||||
ctx := L.CheckString(2)
|
||||
fields := L.CheckTable(3)
|
||||
// hilbish.jobs table
|
||||
jobs = newJobHandler()
|
||||
jobModule := jobs.loader(rtm)
|
||||
util.Document(jobModule, "(Background) job interface.")
|
||||
mod.Set(rt.StringValue("jobs"), rt.TableValue(jobModule))
|
||||
|
||||
timers = newTimerHandler()
|
||||
timerModule := timers.loader(rtm)
|
||||
util.Document(timerModule, "Timer interface, for control of all intervals and timeouts.")
|
||||
mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule))
|
||||
|
||||
var fds []string
|
||||
fields.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||
fds = append(fds, v.String())
|
||||
})
|
||||
|
||||
completions := fileComplete(query, ctx, fds)
|
||||
luaComps := L.NewTable()
|
||||
|
||||
for _, comp := range completions {
|
||||
luaComps.Append(lua.LString(comp))
|
||||
}
|
||||
|
||||
L.Push(luaComps)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func luaBinaryComplete(L *lua.LState) int {
|
||||
query := L.CheckString(1)
|
||||
ctx := L.CheckString(2)
|
||||
fields := L.CheckTable(3)
|
||||
|
||||
var fds []string
|
||||
fields.ForEach(func(k lua.LValue, v lua.LValue) {
|
||||
fds = append(fds, v.String())
|
||||
})
|
||||
|
||||
completions, _ := binaryComplete(query, ctx, fds)
|
||||
luaComps := L.NewTable()
|
||||
|
||||
for _, comp := range completions {
|
||||
luaComps.Append(lua.LString(comp))
|
||||
}
|
||||
|
||||
L.Push(luaComps)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
func setVimMode(mode string) {
|
||||
util.SetField(l, hshMod, "vimMode", lua.LString(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||
hooks.Em.Emit("hilbish.vimMode", mode)
|
||||
}
|
||||
|
||||
func unsetVimMode() {
|
||||
util.SetField(l, hshMod, "vimMode", lua.LNil, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||
}
|
||||
|
||||
// run(cmd)
|
||||
// Runs `cmd` in Hilbish's sh interpreter.
|
||||
// --- @param cmd string
|
||||
func hlrun(L *lua.LState) int {
|
||||
var exitcode uint8
|
||||
cmd := L.CheckString(1)
|
||||
err := execCommand(cmd, cmd)
|
||||
|
||||
if code, ok := interp.IsExitStatus(err); ok {
|
||||
exitcode = code
|
||||
} else if err != nil {
|
||||
exitcode = 1
|
||||
}
|
||||
|
||||
L.Push(lua.LNumber(exitcode))
|
||||
return 1
|
||||
}
|
||||
|
||||
// cwd()
|
||||
// Returns the current directory of the shell
|
||||
func hlcwd(L *lua.LState) int {
|
||||
cwd, _ := os.Getwd()
|
||||
|
||||
L.Push(lua.LString(cwd))
|
||||
|
||||
return 1
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
func getenv(key, fallback string) string {
|
||||
|
@ -200,28 +148,164 @@ func getenv(key, fallback string) string {
|
|||
return value
|
||||
}
|
||||
|
||||
func luaFileComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
query, ctx, fds, err := getCompleteParams(t, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
completions, _ := fileComplete(query, ctx, fds)
|
||||
luaComps := rt.NewTable()
|
||||
|
||||
for i, comp := range completions {
|
||||
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
|
||||
}
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(luaComps)), nil
|
||||
}
|
||||
|
||||
func luaBinaryComplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
query, ctx, fds, err := getCompleteParams(t, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
completions, _ := binaryComplete(query, ctx, fds)
|
||||
luaComps := rt.NewTable()
|
||||
|
||||
for i, comp := range completions {
|
||||
luaComps.Set(rt.IntValue(int64(i + 1)), rt.StringValue(comp))
|
||||
}
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(luaComps)), nil
|
||||
}
|
||||
|
||||
func getCompleteParams(t *rt.Thread, c *rt.GoCont) (string, string, []string, error) {
|
||||
if err := c.CheckNArgs(3); err != nil {
|
||||
return "", "", []string{}, err
|
||||
}
|
||||
query, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return "", "", []string{}, err
|
||||
}
|
||||
ctx, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return "", "", []string{}, err
|
||||
}
|
||||
fields, err := c.TableArg(2)
|
||||
if err != nil {
|
||||
return "", "", []string{}, err
|
||||
}
|
||||
|
||||
var fds []string
|
||||
nextVal := rt.NilValue
|
||||
for {
|
||||
next, val, ok := fields.Next(nextVal)
|
||||
if next == rt.NilValue {
|
||||
break
|
||||
}
|
||||
nextVal = next
|
||||
|
||||
valStr, ok := val.TryString()
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
fds = append(fds, valStr)
|
||||
}
|
||||
|
||||
return query, ctx, fds, err
|
||||
}
|
||||
|
||||
func setVimMode(mode string) {
|
||||
util.SetField(l, hshMod, "vimMode", rt.StringValue(mode), "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||
hooks.Em.Emit("hilbish.vimMode", mode)
|
||||
}
|
||||
|
||||
func unsetVimMode() {
|
||||
util.SetField(l, hshMod, "vimMode", rt.NilValue, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
||||
}
|
||||
|
||||
// run(cmd, returnOut) -> exitCode, stdout, stderr
|
||||
// Runs `cmd` in Hilbish's sh interpreter.
|
||||
// If returnOut is true, the outputs of `cmd` will be returned as the 2nd and
|
||||
// 3rd values instead of being outputted to the terminal.
|
||||
// --- @param cmd string
|
||||
func hlrun(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var terminalOut bool
|
||||
if len(c.Etc()) != 0 {
|
||||
tout := c.Etc()[0]
|
||||
termOut, ok := tout.TryBool()
|
||||
terminalOut = termOut
|
||||
if !ok {
|
||||
return nil, errors.New("bad argument to run (expected boolean, got " + tout.TypeName() + ")")
|
||||
}
|
||||
} else {
|
||||
terminalOut = true
|
||||
}
|
||||
|
||||
var exitcode uint8
|
||||
stdout, stderr, err := execCommand(cmd, terminalOut)
|
||||
|
||||
if code, ok := interp.IsExitStatus(err); ok {
|
||||
exitcode = code
|
||||
} else if err != nil {
|
||||
exitcode = 1
|
||||
}
|
||||
|
||||
stdoutStr := ""
|
||||
stderrStr := ""
|
||||
if !terminalOut {
|
||||
stdoutStr = stdout.(*bytes.Buffer).String()
|
||||
stderrStr = stderr.(*bytes.Buffer).String()
|
||||
}
|
||||
|
||||
return c.PushingNext(t.Runtime, rt.IntValue(int64(exitcode)), rt.StringValue(stdoutStr), rt.StringValue(stderrStr)), nil
|
||||
}
|
||||
|
||||
// cwd()
|
||||
// Returns the current directory of the shell
|
||||
func hlcwd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
cwd, _ := os.Getwd()
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(cwd)), nil
|
||||
}
|
||||
|
||||
|
||||
// read(prompt) -> input?
|
||||
// Read input from the user, using Hilbish's line editor/input reader.
|
||||
// This is a separate instance from the one Hilbish actually uses.
|
||||
// Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
|
||||
// --- @param prompt string
|
||||
func hlread(L *lua.LState) int {
|
||||
luaprompt := L.CheckString(1)
|
||||
func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
luaprompt, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lualr := newLineReader("", true)
|
||||
lualr.SetPrompt(luaprompt)
|
||||
|
||||
input, err := lualr.Read()
|
||||
if err != nil {
|
||||
L.Push(lua.LNil)
|
||||
return 1
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
L.Push(lua.LString(input))
|
||||
return 1
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(input)), nil
|
||||
}
|
||||
|
||||
/*
|
||||
prompt(str)
|
||||
prompt(str, typ?)
|
||||
Changes the shell prompt to `str`
|
||||
There are a few verbs that can be used in the prompt text.
|
||||
These will be formatted and replaced with the appropriate values.
|
||||
|
@ -229,53 +313,110 @@ These will be formatted and replaced with the appropriate values.
|
|||
`%u` - Name of current user
|
||||
`%h` - Hostname of device
|
||||
--- @param str string
|
||||
--- @param typ string Type of prompt, being left or right. Left by default.
|
||||
*/
|
||||
func hlprompt(L *lua.LState) int {
|
||||
prompt = L.CheckString(1)
|
||||
lr.SetPrompt(fmtPrompt(prompt))
|
||||
func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
err := c.Check1Arg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typ := "left"
|
||||
// optional 2nd arg
|
||||
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() + ")")
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
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
|
||||
}
|
||||
|
||||
// multiprompt(str)
|
||||
// Changes the continued line prompt to `str`
|
||||
// --- @param str string
|
||||
func hlmlprompt(L *lua.LState) int {
|
||||
multilinePrompt = L.CheckString(1)
|
||||
func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prompt, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
multilinePrompt = prompt
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// alias(cmd, orig)
|
||||
// Sets an alias of `orig` to `cmd`
|
||||
// Sets an alias of `cmd` to `orig`
|
||||
// --- @param cmd string
|
||||
// --- @param orig string
|
||||
func hlalias(L *lua.LState) int {
|
||||
alias := L.CheckString(1)
|
||||
source := L.CheckString(2)
|
||||
func hlalias(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
orig, err := c.StringArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
aliases.Add(alias, source)
|
||||
aliases.Add(cmd, orig)
|
||||
|
||||
return 1
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// appendPath(dir)
|
||||
// Appends `dir` to $PATH
|
||||
// --- @param dir string|table
|
||||
func hlappendPath(L *lua.LState) int {
|
||||
func hlappendPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arg := c.Arg(0)
|
||||
|
||||
// check if dir is a table or a string
|
||||
arg := L.Get(1)
|
||||
if arg.Type() == lua.LTTable {
|
||||
arg.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) {
|
||||
appendPath(v.String())
|
||||
})
|
||||
} else if arg.Type() == lua.LTString {
|
||||
appendPath(arg.String())
|
||||
if arg.Type() == rt.TableType {
|
||||
nextVal := rt.NilValue
|
||||
for {
|
||||
next, val, ok := arg.AsTable().Next(nextVal)
|
||||
if next == rt.NilValue {
|
||||
break
|
||||
}
|
||||
nextVal = next
|
||||
|
||||
valStr, ok := val.TryString()
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
appendPath(valStr)
|
||||
}
|
||||
} else if arg.Type() == rt.StringType {
|
||||
appendPath(arg.AsString())
|
||||
} else {
|
||||
L.RaiseError("bad argument to appendPath (expected string or table, got %v)", L.Get(1).Type().String())
|
||||
return nil, errors.New("bad argument to appendPath (expected string or table, got " + arg.TypeName() + ")")
|
||||
}
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func appendPath(dir string) {
|
||||
|
@ -291,8 +432,14 @@ func appendPath(dir string) {
|
|||
// exec(cmd)
|
||||
// Replaces running hilbish with `cmd`
|
||||
// --- @param cmd string
|
||||
func hlexec(L *lua.LState) int {
|
||||
cmd := L.CheckString(1)
|
||||
func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmdArgs, _ := splitInput(cmd)
|
||||
if runtime.GOOS != "windows" {
|
||||
cmdPath, err := exec.LookPath(cmdArgs[0])
|
||||
|
@ -314,88 +461,82 @@ func hlexec(L *lua.LState) int {
|
|||
os.Exit(0)
|
||||
}
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// goro(fn)
|
||||
// Puts `fn` in a goroutine
|
||||
// --- @param fn function
|
||||
func hlgoro(L *lua.LState) int {
|
||||
fn := L.CheckFunction(1)
|
||||
argnum := L.GetTop()
|
||||
args := make([]lua.LValue, argnum)
|
||||
for i := 1; i <= argnum; i++ {
|
||||
args[i - 1] = L.Get(i)
|
||||
func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fn, err := c.ClosureArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// call fn
|
||||
go func() {
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: fn,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}, args...); err != nil {
|
||||
_, err := rt.Call1(l.MainThread(), rt.FunctionValue(fn), c.Etc()...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error in goro function:\n\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// timeout(cb, time)
|
||||
// Runs the `cb` function after `time` in milliseconds
|
||||
// Returns a `timer` object (see `doc timers`).
|
||||
// --- @param cb function
|
||||
// --- @param time number
|
||||
func hltimeout(L *lua.LState) int {
|
||||
cb := L.CheckFunction(1)
|
||||
ms := L.CheckInt(2)
|
||||
|
||||
timeout := time.Duration(ms) * time.Millisecond
|
||||
time.Sleep(timeout)
|
||||
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: cb,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error in goro function:\n\n", err)
|
||||
// --- @return table
|
||||
func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return 0
|
||||
cb, err := c.ClosureArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms, err := c.IntArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interval := time.Duration(ms) * time.Millisecond
|
||||
timer := timers.create(timerTimeout, interval, cb)
|
||||
timer.start()
|
||||
|
||||
return c.PushingNext1(t.Runtime, timer.lua()), nil
|
||||
}
|
||||
|
||||
// interval(cb, time)
|
||||
// Runs the `cb` function every `time` milliseconds
|
||||
// Runs the `cb` function every `time` milliseconds.
|
||||
// Returns a `timer` object (see `doc timers`).
|
||||
// --- @param cb function
|
||||
// --- @param time number
|
||||
func hlinterval(L *lua.LState) int {
|
||||
intervalfunc := L.CheckFunction(1)
|
||||
ms := L.CheckInt(2)
|
||||
// --- @return table
|
||||
func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cb, err := c.ClosureArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms, err := c.IntArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
interval := time.Duration(ms) * time.Millisecond
|
||||
timer := timers.create(timerInterval, interval, cb)
|
||||
timer.start()
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
stop := make(chan lua.LValue)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := L.CallByParam(lua.P{
|
||||
Fn: intervalfunc,
|
||||
NRet: 0,
|
||||
Protect: true,
|
||||
}); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error in interval function:\n\n", err)
|
||||
stop <- lua.LTrue // stop the interval
|
||||
}
|
||||
case <-stop:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
L.Push(lua.LChannel(stop))
|
||||
return 1
|
||||
return c.PushingNext1(t.Runtime, timer.lua()), nil
|
||||
}
|
||||
|
||||
// complete(scope, cb)
|
||||
|
@ -408,20 +549,27 @@ func hlinterval(L *lua.LState) int {
|
|||
// `grid` (the normal file completion display) or `list` (with a description)
|
||||
// --- @param scope string
|
||||
// --- @param cb function
|
||||
func hlcomplete(L *lua.LState) int {
|
||||
scope := L.CheckString(1)
|
||||
cb := L.CheckFunction(2)
|
||||
|
||||
func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
scope, cb, err := util.HandleStrCallback(t, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
luaCompletions[scope] = cb
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// prependPath(dir)
|
||||
// Prepends `dir` to $PATH
|
||||
// --- @param dir string
|
||||
func hlprependPath(L *lua.LState) int {
|
||||
dir := L.CheckString(1)
|
||||
func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir = strings.Replace(dir, "~", curuser.HomeDir, 1)
|
||||
pathenv := os.Getenv("PATH")
|
||||
|
||||
|
@ -430,29 +578,40 @@ func hlprependPath(L *lua.LState) int {
|
|||
os.Setenv("PATH", dir + string(os.PathListSeparator) + pathenv)
|
||||
}
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// which(binName)
|
||||
// Searches for an executable called `binName` in the directories of $PATH
|
||||
// --- @param binName string
|
||||
func hlwhich(L *lua.LState) int {
|
||||
binName := L.CheckString(1)
|
||||
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
binName, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path, err := exec.LookPath(binName)
|
||||
if err != nil {
|
||||
l.Push(lua.LNil)
|
||||
return 1
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
l.Push(lua.LString(path))
|
||||
return 1
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(path)), nil
|
||||
}
|
||||
|
||||
// inputMode(mode)
|
||||
// Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
||||
// --- @param mode string
|
||||
func hlinputMode(L *lua.LState) int {
|
||||
mode := L.CheckString(1)
|
||||
func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mode, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case "emacs":
|
||||
unsetVimMode()
|
||||
|
@ -460,7 +619,74 @@ func hlinputMode(L *lua.LState) int {
|
|||
case "vim":
|
||||
setVimMode("insert")
|
||||
lr.rl.InputMode = readline.Vim
|
||||
default: L.RaiseError("inputMode: expected vim or emacs, received " + mode)
|
||||
default:
|
||||
return nil, errors.New("inputMode: expected vim or emacs, received " + mode)
|
||||
}
|
||||
return 0
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// runnerMode(mode)
|
||||
// Sets the execution/runner mode for interactive Hilbish. This determines whether
|
||||
// Hilbish wll try to run input as Lua and/or sh or only do one of either.
|
||||
// Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||
// sh, and lua. It also accepts a function, to which if it is passed one
|
||||
// will call it to execute user input instead.
|
||||
// --- @param mode string|function
|
||||
func hlrunnerMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mode := c.Arg(0)
|
||||
|
||||
switch mode.Type() {
|
||||
case rt.StringType:
|
||||
switch mode.AsString() {
|
||||
// no fallthrough doesnt work so eh
|
||||
case "hybrid", "hybridRev", "lua", "sh": runnerMode = mode
|
||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.AsString())
|
||||
}
|
||||
case rt.FunctionType: runnerMode = mode
|
||||
default: return nil, errors.New("execMode: expected either a function or hybrid, hybridRev, lua, sh. Received " + mode.TypeName())
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// hinter(cb)
|
||||
// Sets the hinter function. This will be called on every key insert to determine
|
||||
// what text to use as an inline hint. The callback is passed 2 arguments:
|
||||
// the current line and the position. It is expected to return a string
|
||||
// which will be used for the hint.
|
||||
// --- @param cb function
|
||||
func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hinterCb, err := c.ClosureArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hinter = hinterCb
|
||||
|
||||
return c.Next(), err
|
||||
}
|
||||
|
||||
// highlighter(cb)
|
||||
// Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
||||
// reality could set the input of the prompt to display anything. The callback
|
||||
// is passed the current line as typed and is expected to return a line that will
|
||||
// be used to display in the line.
|
||||
// --- @param cb function
|
||||
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
highlighterCb, err := c.ClosureArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
highlighter = highlighterCb
|
||||
|
||||
return c.Next(), err
|
||||
}
|
||||
|
|
|
@ -80,6 +80,9 @@ func main() {
|
|||
if emmyType == "@param" {
|
||||
em.Params = append(em.Params, emmyLinePieces[1])
|
||||
}
|
||||
if emmyType == "@vararg" {
|
||||
em.Params = append(em.Params, "...") // add vararg
|
||||
}
|
||||
em.Docs = append(em.Docs, d)
|
||||
} else {
|
||||
funcdoc = append(funcdoc, d)
|
||||
|
@ -111,6 +114,9 @@ func main() {
|
|||
if emmyType == "@param" {
|
||||
em.Params = append(em.Params, emmyLinePieces[1])
|
||||
}
|
||||
if emmyType == "@vararg" {
|
||||
em.Params = append(em.Params, "...") // add vararg
|
||||
}
|
||||
em.Docs = append(em.Docs, d)
|
||||
} else {
|
||||
funcdoc = append(funcdoc, d)
|
||||
|
|
110
complete.go
110
complete.go
|
@ -2,27 +2,12 @@ package main
|
|||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"os"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
func fileComplete(query, ctx string, fields []string) []string {
|
||||
var completions []string
|
||||
|
||||
prefixes := []string{"./", "../", "/", "~/"}
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(query, prefix) {
|
||||
completions, _ = matchPath(strings.Replace(query, "~", curuser.HomeDir, 1), query)
|
||||
}
|
||||
}
|
||||
|
||||
if len(completions) == 0 && len(fields) > 1 {
|
||||
completions, _ = matchPath("./" + query, query)
|
||||
}
|
||||
|
||||
return completions
|
||||
func fileComplete(query, ctx string, fields []string) ([]string, string) {
|
||||
return matchPath(query)
|
||||
}
|
||||
|
||||
func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||
|
@ -31,17 +16,17 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
|||
prefixes := []string{"./", "../", "/", "~/"}
|
||||
for _, prefix := range prefixes {
|
||||
if strings.HasPrefix(query, prefix) {
|
||||
fileCompletions := fileComplete(query, ctx, fields)
|
||||
fileCompletions, filePref := matchPath(query)
|
||||
if len(fileCompletions) != 0 {
|
||||
for _, f := range fileCompletions {
|
||||
name := strings.Replace(query + f, "~", curuser.HomeDir, 1)
|
||||
if info, err := os.Stat(name); err == nil && info.Mode().Perm() & 0100 == 0 {
|
||||
fullPath, _ := filepath.Abs(expandHome(query + strings.TrimPrefix(f, filePref)))
|
||||
if err := findExecutable(fullPath, false, true); err != nil {
|
||||
continue
|
||||
}
|
||||
completions = append(completions, f)
|
||||
}
|
||||
}
|
||||
return completions, ""
|
||||
return completions, filePref
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +38,8 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
|||
// get basename from matches
|
||||
for _, match := range matches {
|
||||
// check if we have execute permissions for our match
|
||||
if info, err := os.Stat(match); err == nil && info.Mode().Perm() & 0100 == 0 {
|
||||
err := findExecutable(match, true, false)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// get basename from match
|
||||
|
@ -76,55 +62,53 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
|||
return completions, query
|
||||
}
|
||||
|
||||
func matchPath(path, pref string) ([]string, error) {
|
||||
func matchPath(query string) ([]string, string) {
|
||||
var entries []string
|
||||
matches, err := filepath.Glob(desensitize(path) + "*")
|
||||
if err == nil {
|
||||
args := []string{
|
||||
"\"", "\\\"",
|
||||
"'", "\\'",
|
||||
"`", "\\`",
|
||||
" ", "\\ ",
|
||||
"(", "\\(",
|
||||
")", "\\)",
|
||||
"[", "\\[",
|
||||
"]", "\\]",
|
||||
}
|
||||
var baseName string
|
||||
|
||||
r := strings.NewReplacer(args...)
|
||||
for _, match := range matches {
|
||||
name := filepath.Base(match)
|
||||
p := filepath.Base(pref)
|
||||
if pref == "" {
|
||||
p = ""
|
||||
path, _ := filepath.Abs(expandHome(filepath.Dir(query)))
|
||||
if string(query) == "" {
|
||||
// filepath base below would give us "."
|
||||
// which would cause a match of only dotfiles
|
||||
path, _ = filepath.Abs(".")
|
||||
} else if !strings.HasSuffix(query, string(os.PathSeparator)) {
|
||||
baseName = filepath.Base(query)
|
||||
}
|
||||
|
||||
files, _ := os.ReadDir(path)
|
||||
for _, file := range files {
|
||||
if strings.HasPrefix(strings.ToLower(file.Name()), strings.ToLower(baseName)) {
|
||||
entry := file.Name()
|
||||
if file.IsDir() {
|
||||
entry = entry + string(os.PathSeparator)
|
||||
}
|
||||
name = strings.TrimPrefix(name, p)
|
||||
matchFull, _ := filepath.Abs(match)
|
||||
if info, err := os.Stat(matchFull); err == nil && info.IsDir() {
|
||||
name = name + string(os.PathSeparator)
|
||||
}
|
||||
name = r.Replace(name)
|
||||
entries = append(entries, name)
|
||||
entry = escapeFilename(entry)
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return entries, err
|
||||
return entries, baseName
|
||||
}
|
||||
|
||||
func desensitize(text string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return text
|
||||
func escapeFilename(fname string) string {
|
||||
args := []string{
|
||||
"\"", "\\\"",
|
||||
"'", "\\'",
|
||||
"`", "\\`",
|
||||
" ", "\\ ",
|
||||
"(", "\\(",
|
||||
")", "\\)",
|
||||
"[", "\\[",
|
||||
"]", "\\]",
|
||||
"$", "\\$",
|
||||
"&", "\\&",
|
||||
"*", "\\*",
|
||||
">", "\\>",
|
||||
"<", "\\<",
|
||||
"|", "\\|",
|
||||
}
|
||||
|
||||
p := strings.Builder{}
|
||||
|
||||
for _, r := range text {
|
||||
if unicode.IsLetter(r) {
|
||||
p.WriteString("[" + string(unicode.ToLower(r)) + string(unicode.ToUpper(r)) + "]")
|
||||
} else {
|
||||
p.WriteString(string(r))
|
||||
}
|
||||
}
|
||||
|
||||
return p.String()
|
||||
r := strings.NewReplacer(args...)
|
||||
return r.Replace(fname)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
alias(cmd, orig) > Sets an alias of `orig` to `cmd`
|
||||
alias(cmd, orig) > Sets an alias of `cmd` to `orig`
|
||||
|
||||
appendPath(dir) > Appends `dir` to $PATH
|
||||
|
||||
|
@ -16,15 +16,26 @@ exec(cmd) > Replaces running hilbish with `cmd`
|
|||
|
||||
goro(fn) > Puts `fn` in a goroutine
|
||||
|
||||
highlighter(cb) > Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
||||
reality could set the input of the prompt to display anything. The callback
|
||||
is passed the current line as typed and is expected to return a line that will
|
||||
be used to display in the line.
|
||||
|
||||
hinter(cb) > Sets the hinter function. This will be called on every key insert to determine
|
||||
what text to use as an inline hint. The callback is passed 2 arguments:
|
||||
the current line and the position. It is expected to return a string
|
||||
which will be used for the hint.
|
||||
|
||||
inputMode(mode) > Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
||||
|
||||
interval(cb, time) > Runs the `cb` function every `time` milliseconds
|
||||
interval(cb, time) > Runs the `cb` function every `time` milliseconds.
|
||||
Returns a `timer` object (see `doc timers`).
|
||||
|
||||
multiprompt(str) > Changes the continued line prompt to `str`
|
||||
|
||||
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.
|
||||
These will be formatted and replaced with the appropriate values.
|
||||
`%d` - Current working directory
|
||||
|
@ -35,9 +46,18 @@ read(prompt) -> input? > Read input from the user, using Hilbish's line editor/i
|
|||
This is a separate instance from the one Hilbish actually uses.
|
||||
Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
|
||||
|
||||
run(cmd) > Runs `cmd` in Hilbish's sh interpreter.
|
||||
run(cmd, returnOut) -> exitCode, stdout, stderr > Runs `cmd` in Hilbish's sh interpreter.
|
||||
If returnOut is true, the outputs of `cmd` will be returned as the 2nd and
|
||||
3rd values instead of being outputted to the terminal.
|
||||
|
||||
runnerMode(mode) > Sets the execution/runner mode for interactive Hilbish. This determines whether
|
||||
Hilbish wll try to run input as Lua and/or sh or only do one of either.
|
||||
Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||
sh, and lua. It also accepts a function, to which if it is passed one
|
||||
will call it to execute user input instead.
|
||||
|
||||
timeout(cb, time) > Runs the `cb` function after `time` in milliseconds
|
||||
Returns a `timer` object (see `doc timers`).
|
||||
|
||||
which(binName) > Searches for an executable called `binName` in the directories of $PATH
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
Note: A `job` is a table with the following keys:
|
||||
- cmd: command string
|
||||
- running: boolean whether the job is running
|
||||
- id: unique id for the job
|
||||
- pid: process id for the job
|
||||
- exitCode: exit code of the job
|
||||
In ordinary cases you'd prefer to use the id instead of pid. The id is unique to
|
||||
Hilbish and is how you get jobs with the `hilbish.jobs` interface.
|
||||
|
||||
+ `job.start` -> job > Thrown when a new background job starts.
|
||||
|
||||
+ `job.done` -> job > Thrown when a background jobs exits.
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
Hilbish is *unique,* when interactive it first attempts to run input as
|
||||
Lua and then tries shell script. But if you're normal, you wouldn't
|
||||
really be using Hilbish anyway but you'd also not want this
|
||||
(or maybe want Lua only in some cases.)
|
||||
|
||||
The "runner mode" of Hilbish is customizable via `hilbish.runnerMode`,
|
||||
which determines how Hilbish will run user input. By default, this is
|
||||
set to `hybrid` which is the previously mentioned behaviour of running Lua
|
||||
first then going to shell script. If you want the reverse order, you can
|
||||
set it to `hybridRev` and for isolated modes there is `sh` and `lua`
|
||||
respectively.
|
||||
|
||||
You can also set it to a function, which will be called everytime Hilbish
|
||||
needs to run interactive input. For example, you can set this to a simple
|
||||
function to compile and evaluate Fennel, and now you can run Fennel.
|
||||
You can even mix it with sh to make a hybrid mode with Lua replaced by
|
||||
Fennel.
|
||||
|
||||
An example:
|
||||
hilbish.runnerMode(function(input)
|
||||
local ok = pcall(fennel.eval, input)
|
||||
if ok then
|
||||
return input, 0, nil
|
||||
end
|
||||
|
||||
return hilbish.runner.sh(input)
|
||||
end)
|
||||
|
||||
The `hilbish.runner` interface is an alternative to using `hilbish.runnerMode`
|
||||
and also provides the sh and Lua runner functions that Hilbish itself uses.
|
||||
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
|
||||
`nil` to indicate no error.
|
||||
|
||||
## Functions
|
||||
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
|
|
@ -1,9 +1,9 @@
|
|||
setRaw() > Puts the terminal in raw mode
|
||||
|
||||
restoreState() > Restores the last saved state of the terminal
|
||||
|
||||
saveState() > Saves the current state of the terminal
|
||||
|
||||
setRaw() > Puts the terminal in raw mode
|
||||
|
||||
size() > Gets the dimensions of the terminal. Returns a table with `width` and `height`
|
||||
Note: this is not the size in relation to the dimensions of the display
|
||||
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
If you ever want to run a piece of code on a timed interval, or want to wait
|
||||
a few seconds, you don't have to rely on timing tricks, as Hilbish has a
|
||||
timer API to set intervals and timeouts.
|
||||
|
||||
These are the simple functions `hilbish.interval` and `hilbish.timeout` (doc
|
||||
accessible with `doc hilbish`). But if you want slightly more control over
|
||||
them, there is the `hilbish.timers` interface. It allows you to get
|
||||
a timer via ID.
|
||||
|
||||
# Timer Interface
|
||||
## Functions
|
||||
- `get(id)` -> timer: get a timer via its id
|
||||
- `create(type, ms, callback)` -> timer: creates a timer, adding it to the timer pool.
|
||||
`type` is the type of timer it will be. 0 is an interval, 1 is a timeout.
|
||||
`ms` is the time it will run for in seconds. callback is the function called
|
||||
when the timer is triggered.
|
||||
|
||||
# Timer Object
|
||||
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
|
||||
- `running`: whether the timer is running or not
|
||||
- `type`: the type of timer (0 is interval, 1 is timeout)
|
||||
|
||||
## Functions
|
||||
- `stop()`: stops the timer. returns an error if it's already stopped
|
||||
- `start()`: starts the timer. returns an error if it's already started
|
|
@ -0,0 +1,16 @@
|
|||
Vim actions are essentially just when a user uses a Vim keybind.
|
||||
Things like yanking and pasting are Vim actions.
|
||||
This is not an "offical Vim thing," just a Hilbish thing.
|
||||
|
||||
The `hilbish.vimAction` hook is thrown whenever a Vim action occurs.
|
||||
It passes 2 arguments: the action name, and an array (table) of args
|
||||
relating to it.
|
||||
|
||||
Here is documentation for what the table of args will hold for an
|
||||
appropriate Vim action.
|
||||
|
||||
- `yank`: register, yankedText
|
||||
The first argument for the yank action is the register yankedText goes to.
|
||||
|
||||
- `paste`: register, pastedText
|
||||
The first argument for the paste action is the register pastedText is taken from.
|
|
@ -0,0 +1,4 @@
|
|||
Hilbish has a Vim binding input mode accessible for use.
|
||||
It can be enabled with the `hilbish.inputMode` function (check `doc hilbish`).
|
||||
|
||||
This is documentation for everything relating to it.
|
|
@ -15,6 +15,6 @@ function bait.catchOnce(name, cb) end
|
|||
--- Throws a hook with `name` with the provided `args`
|
||||
--- @param name string
|
||||
--- @vararg any
|
||||
function bait.throw(name) end
|
||||
function bait.throw(name, ...) end
|
||||
|
||||
return bait
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
local hilbish = {}
|
||||
|
||||
--- Sets an alias of `orig` to `cmd`
|
||||
--- Sets an alias of `cmd` to `orig`
|
||||
--- @param cmd string
|
||||
--- @param orig string
|
||||
function hilbish.alias(cmd, orig) end
|
||||
|
@ -33,18 +33,34 @@ function hilbish.exec(cmd) end
|
|||
--- @param fn function
|
||||
function hilbish.goro(fn) end
|
||||
|
||||
--- Sets the highlighter function. This is mainly for syntax hightlighting, but in
|
||||
--- reality could set the input of the prompt to display anything. The callback
|
||||
--- is passed the current line as typed and is expected to return a line that will
|
||||
--- be used to display in the line.
|
||||
--- @param cb function
|
||||
function hilbish.highlighter(cb) end
|
||||
|
||||
--- Sets the hinter function. This will be called on every key insert to determine
|
||||
--- what text to use as an inline hint. The callback is passed 2 arguments:
|
||||
--- the current line and the position. It is expected to return a string
|
||||
--- which will be used for the hint.
|
||||
--- @param cb function
|
||||
function hilbish.hinter(cb) end
|
||||
|
||||
--- Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
||||
--- @param mode string
|
||||
function hilbish.inputMode(mode) end
|
||||
|
||||
--- Runs the `cb` function every `time` milliseconds
|
||||
--- Runs the `cb` function every `time` milliseconds.
|
||||
--- Returns a `timer` object (see `doc timers`).
|
||||
--- @param cb function
|
||||
--- @param time number
|
||||
--- @return table
|
||||
function hilbish.interval(cb, time) end
|
||||
|
||||
--- Changes the continued line prompt to `str`
|
||||
--- @param str string
|
||||
function hilbish.mlprompt(str) end
|
||||
function hilbish.multiprompt(str) end
|
||||
|
||||
--- Prepends `dir` to $PATH
|
||||
--- @param dir string
|
||||
|
@ -57,7 +73,8 @@ function hilbish.prependPath(dir) end
|
|||
--- `%u` - Name of current user
|
||||
--- `%h` - Hostname of device
|
||||
--- @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.
|
||||
--- This is a separate instance from the one Hilbish actually uses.
|
||||
|
@ -66,12 +83,24 @@ function hilbish.prompt(str) end
|
|||
function hilbish.read(prompt) end
|
||||
|
||||
--- Runs `cmd` in Hilbish's sh interpreter.
|
||||
--- If returnOut is true, the outputs of `cmd` will be returned as the 2nd and
|
||||
--- 3rd values instead of being outputted to the terminal.
|
||||
--- @param cmd string
|
||||
function hilbish.run(cmd) end
|
||||
|
||||
--- Sets the execution/runner mode for interactive Hilbish. This determines whether
|
||||
--- Hilbish wll try to run input as Lua and/or sh or only do one of either.
|
||||
--- Accepted values for mode are hybrid (the default), hybridRev (sh first then Lua),
|
||||
--- sh, and lua. It also accepts a function, to which if it is passed one
|
||||
--- will call it to execute user input instead.
|
||||
--- @param mode string|function
|
||||
function hilbish.runnerMode(mode) end
|
||||
|
||||
--- Runs the `cb` function after `time` in milliseconds
|
||||
--- Returns a `timer` object (see `doc timers`).
|
||||
--- @param cb function
|
||||
--- @param time number
|
||||
--- @return table
|
||||
function hilbish.timeout(cb, time) end
|
||||
|
||||
--- Searches for an executable called `binName` in the directories of $PATH
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
local terminal = {}
|
||||
|
||||
--- Puts the terminal in raw mode
|
||||
function terminal.raw() end
|
||||
|
||||
--- Restores the last saved state of the terminal
|
||||
function terminal.restoreState() end
|
||||
|
||||
--- Saves the current state of the terminal
|
||||
function terminal.saveState() end
|
||||
|
||||
--- Puts the terminal in raw mode
|
||||
function terminal.setRaw() end
|
||||
|
||||
--- Gets the dimensions of the terminal. Returns a table with `width` and `height`
|
||||
--- Note: this is not the size in relation to the dimensions of the display
|
||||
function terminal.size() end
|
||||
|
|
363
exec.go
363
exec.go
|
@ -1,30 +1,109 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"mvdan.cc/sh/v3/shell"
|
||||
//"github.com/yuin/gopher-lua/parse"
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
"mvdan.cc/sh/v3/syntax"
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
)
|
||||
|
||||
func runInput(input, origInput string) {
|
||||
var errNotExec = errors.New("not executable")
|
||||
var runnerMode rt.Value = rt.StringValue("hybrid")
|
||||
|
||||
func runInput(input string, priv bool) {
|
||||
running = true
|
||||
cmdString := aliases.Resolve(input)
|
||||
|
||||
hooks.Em.Emit("command.preexec", input, cmdString)
|
||||
|
||||
var exitCode uint8
|
||||
var err error
|
||||
if runnerMode.Type() == rt.StringType {
|
||||
switch runnerMode.AsString() {
|
||||
case "hybrid":
|
||||
_, _, err = handleLua(cmdString)
|
||||
if err == nil {
|
||||
cmdFinish(0, input, priv)
|
||||
return
|
||||
}
|
||||
input, exitCode, err = handleSh(input)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
case "hybridRev":
|
||||
_, _, err = handleSh(input)
|
||||
if err == nil {
|
||||
cmdFinish(0, input, priv)
|
||||
return
|
||||
}
|
||||
input, exitCode, err = handleLua(cmdString)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
case "lua":
|
||||
input, exitCode, err = handleLua(cmdString)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
case "sh":
|
||||
input, exitCode, err = handleSh(input)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
}
|
||||
} else {
|
||||
// can only be a string or function so
|
||||
term := rt.NewTerminationWith(l.MainThread().CurrentCont(), 2, false)
|
||||
err := rt.Call(l.MainThread(), runnerMode, []rt.Value{rt.StringValue(cmdString)}, term)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
cmdFinish(124, input, priv)
|
||||
return
|
||||
}
|
||||
|
||||
luaexitcode := term.Get(0)
|
||||
runErr := term.Get(1)
|
||||
luaInput := term.Get(1)
|
||||
|
||||
var exitCode uint8
|
||||
if code, ok := luaexitcode.TryInt(); ok {
|
||||
exitCode = uint8(code)
|
||||
}
|
||||
|
||||
if inp, ok := luaInput.TryString(); ok {
|
||||
input = inp
|
||||
}
|
||||
|
||||
if runErr != rt.NilValue {
|
||||
fmt.Fprintln(os.Stderr, runErr)
|
||||
}
|
||||
cmdFinish(exitCode, input, priv)
|
||||
}
|
||||
}
|
||||
|
||||
func handleLua(cmdString string) (string, uint8, error) {
|
||||
// First try to load input, essentially compiling to bytecode
|
||||
fn, err := l.LoadString(cmdString)
|
||||
chunk, err := l.CompileAndLoadLuaChunk("", []byte(cmdString), rt.TableValue(l.GlobalEnv()))
|
||||
if err != nil && noexecute {
|
||||
fmt.Println(err)
|
||||
/* if lerr, ok := err.(*lua.ApiError); ok {
|
||||
|
@ -33,62 +112,102 @@ func runInput(input, origInput string) {
|
|||
}
|
||||
}
|
||||
*/
|
||||
return
|
||||
return cmdString, 125, err
|
||||
}
|
||||
// And if there's no syntax errors and -n isnt provided, run
|
||||
if !noexecute {
|
||||
l.Push(fn)
|
||||
err = l.PCall(0, lua.MultRet, nil)
|
||||
if chunk != nil {
|
||||
_, err = rt.Call1(l.MainThread(), rt.FunctionValue(chunk))
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
cmdFinish(0, cmdString, origInput)
|
||||
return
|
||||
return cmdString, 0, nil
|
||||
}
|
||||
|
||||
// Last option: use sh interpreter
|
||||
err = execCommand(cmdString, origInput)
|
||||
return cmdString, 125, err
|
||||
}
|
||||
|
||||
func handleSh(cmdString string) (string, uint8, error) {
|
||||
_, _, err := execCommand(cmdString, true)
|
||||
if err != nil {
|
||||
// If input is incomplete, start multiline prompting
|
||||
if syntax.IsIncomplete(err) {
|
||||
if !interactive {
|
||||
return cmdString, 126, err
|
||||
}
|
||||
for {
|
||||
cmdString, err = continuePrompt(strings.TrimSuffix(cmdString, "\\"))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
err = execCommand(cmdString, origInput)
|
||||
if syntax.IsIncomplete(err) || strings.HasSuffix(input, "\\") {
|
||||
_, _, err = execCommand(cmdString, true)
|
||||
if syntax.IsIncomplete(err) || strings.HasSuffix(cmdString, "\\") {
|
||||
continue
|
||||
} else if code, ok := interp.IsExitStatus(err); ok {
|
||||
cmdFinish(code, cmdString, origInput)
|
||||
return cmdString, code, nil
|
||||
} else if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
cmdFinish(1, cmdString, origInput)
|
||||
return cmdString, 126, err
|
||||
} else {
|
||||
cmdFinish(0, cmdString, origInput)
|
||||
return cmdString, 0, nil
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if code, ok := interp.IsExitStatus(err); ok {
|
||||
cmdFinish(code, cmdString, origInput)
|
||||
return cmdString, code, nil
|
||||
} else {
|
||||
cmdFinish(126, cmdString, origInput)
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return cmdString, 126, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cmdFinish(0, cmdString, origInput)
|
||||
}
|
||||
|
||||
return cmdString, 0, nil
|
||||
}
|
||||
|
||||
// Run command in sh interpreter
|
||||
func execCommand(cmd, old string) error {
|
||||
func execCommand(cmd string, terminalOut bool) (io.Writer, io.Writer, error) {
|
||||
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
exechandle := func(ctx context.Context, args []string) error {
|
||||
runner, _ := interp.New()
|
||||
|
||||
var stdout io.Writer
|
||||
var stderr io.Writer
|
||||
if terminalOut {
|
||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr)(runner)
|
||||
} else {
|
||||
stdout = new(bytes.Buffer)
|
||||
stderr = new(bytes.Buffer)
|
||||
interp.StdIO(os.Stdin, stdout, stderr)(runner)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
printer := syntax.NewPrinter()
|
||||
|
||||
var bg bool
|
||||
for _, stmt := range file.Stmts {
|
||||
bg = false
|
||||
if stmt.Background {
|
||||
bg = true
|
||||
printer.Print(buf, stmt.Cmd)
|
||||
|
||||
stmtStr := buf.String()
|
||||
buf.Reset()
|
||||
jobs.add(stmtStr)
|
||||
}
|
||||
|
||||
interp.ExecHandler(execHandle(bg))(runner)
|
||||
err = runner.Run(context.TODO(), stmt)
|
||||
if err != nil {
|
||||
return stdout, stderr, err
|
||||
}
|
||||
}
|
||||
|
||||
return stdout, stderr, nil
|
||||
}
|
||||
|
||||
func execHandle(bg bool) interp.ExecHandlerFunc {
|
||||
return func(ctx context.Context, args []string) error {
|
||||
_, argstring := splitInput(strings.Join(args, " "))
|
||||
// i dont really like this but it works
|
||||
if aliases.All()[args[0]] != "" {
|
||||
|
@ -101,74 +220,176 @@ func execCommand(cmd, old string) error {
|
|||
|
||||
// If alias was found, use command alias
|
||||
argstring = aliases.Resolve(argstring)
|
||||
args, _ = shell.Fields(argstring, nil)
|
||||
var err error
|
||||
args, err = shell.Fields(argstring, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If command is defined in Lua then run it
|
||||
luacmdArgs := l.NewTable()
|
||||
for _, str := range args[1:] {
|
||||
luacmdArgs.Append(lua.LString(str))
|
||||
luacmdArgs := rt.NewTable()
|
||||
for i, str := range args[1:] {
|
||||
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
|
||||
}
|
||||
|
||||
if commands[args[0]] != nil {
|
||||
err := l.CallByParam(lua.P{
|
||||
Fn: commands[args[0]],
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, luacmdArgs)
|
||||
|
||||
luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr,
|
||||
"Error in command:\n\n" + err.Error())
|
||||
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
|
||||
return interp.NewExitStatus(1)
|
||||
}
|
||||
|
||||
luaexitcode := l.Get(-1)
|
||||
var exitcode uint8
|
||||
|
||||
l.Pop(1)
|
||||
|
||||
if code, ok := luaexitcode.(lua.LNumber); luaexitcode != lua.LNil && ok {
|
||||
if code, ok := luaexitcode.TryInt(); ok {
|
||||
exitcode = uint8(code)
|
||||
} else if luaexitcode != rt.NilValue {
|
||||
// deregister commander
|
||||
delete(commands, args[0])
|
||||
fmt.Fprintf(os.Stderr, "Commander did not return number for exit code. %s, you're fired.\n", args[0])
|
||||
}
|
||||
|
||||
cmdFinish(exitcode, argstring, old)
|
||||
return interp.NewExitStatus(exitcode)
|
||||
}
|
||||
|
||||
err := lookpath(args[0])
|
||||
if err == os.ErrPermission {
|
||||
if err == errNotExec {
|
||||
hooks.Em.Emit("command.no-perm", args[0])
|
||||
hooks.Em.Emit("command.not-executable", args[0])
|
||||
return interp.NewExitStatus(126)
|
||||
} else if err != nil {
|
||||
hooks.Em.Emit("command.not-found", args[0])
|
||||
return interp.NewExitStatus(127)
|
||||
}
|
||||
|
||||
return interp.DefaultExecHandler(2 * time.Second)(ctx, args)
|
||||
}
|
||||
runner, _ := interp.New(
|
||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||
interp.ExecHandler(exechandle),
|
||||
)
|
||||
err = runner.Run(context.TODO(), file)
|
||||
killTimeout := 2 * time.Second
|
||||
// from here is basically copy-paste of the default exec handler from
|
||||
// sh/interp but with our job handling
|
||||
hc := interp.HandlerCtx(ctx)
|
||||
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
|
||||
if err != nil {
|
||||
fmt.Fprintln(hc.Stderr, err)
|
||||
return interp.NewExitStatus(127)
|
||||
}
|
||||
|
||||
return err
|
||||
env := hc.Env
|
||||
envList := make([]string, 0, 64)
|
||||
env.Each(func(name string, vr expand.Variable) bool {
|
||||
if !vr.IsSet() {
|
||||
// If a variable is set globally but unset in the
|
||||
// runner, we need to ensure it's not part of the final
|
||||
// list. Seems like zeroing the element is enough.
|
||||
// This is a linear search, but this scenario should be
|
||||
// rare, and the number of variables shouldn't be large.
|
||||
for i, kv := range envList {
|
||||
if strings.HasPrefix(kv, name+"=") {
|
||||
envList[i] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
if vr.Exported && vr.Kind == expand.String {
|
||||
envList = append(envList, name+"="+vr.String())
|
||||
}
|
||||
return true
|
||||
})
|
||||
cmd := exec.Cmd{
|
||||
Path: path,
|
||||
Args: args,
|
||||
Env: envList,
|
||||
Dir: hc.Dir,
|
||||
Stdin: hc.Stdin,
|
||||
Stdout: hc.Stdout,
|
||||
Stderr: hc.Stderr,
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
var j *job
|
||||
if bg {
|
||||
j = jobs.getLatest()
|
||||
j.setHandle(cmd.Process)
|
||||
}
|
||||
if err == nil {
|
||||
if bg {
|
||||
j.start(cmd.Process.Pid)
|
||||
}
|
||||
|
||||
if done := ctx.Done(); done != nil {
|
||||
go func() {
|
||||
<-done
|
||||
|
||||
if killTimeout <= 0 || runtime.GOOS == "windows" {
|
||||
cmd.Process.Signal(os.Kill)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: don't temporarily leak this goroutine
|
||||
// if the program stops itself with the
|
||||
// interrupt.
|
||||
go func() {
|
||||
time.Sleep(killTimeout)
|
||||
cmd.Process.Signal(os.Kill)
|
||||
}()
|
||||
cmd.Process.Signal(os.Interrupt)
|
||||
}()
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
}
|
||||
|
||||
var exit uint8
|
||||
switch x := err.(type) {
|
||||
case *exec.ExitError:
|
||||
// started, but errored - default to 1 if OS
|
||||
// doesn't have exit statuses
|
||||
if status, ok := x.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Signaled() {
|
||||
if ctx.Err() != nil {
|
||||
return ctx.Err()
|
||||
}
|
||||
exit = uint8(128 + status.Signal())
|
||||
goto end
|
||||
}
|
||||
exit = uint8(status.ExitStatus())
|
||||
goto end
|
||||
}
|
||||
exit = 1
|
||||
goto end
|
||||
case *exec.Error:
|
||||
// did not start
|
||||
fmt.Fprintf(hc.Stderr, "%v\n", err)
|
||||
exit = 127
|
||||
goto end
|
||||
case nil:
|
||||
goto end
|
||||
default:
|
||||
return err
|
||||
}
|
||||
end:
|
||||
if bg {
|
||||
j.exitCode = int(exit)
|
||||
j.finish()
|
||||
}
|
||||
return interp.NewExitStatus(exit)
|
||||
}
|
||||
}
|
||||
|
||||
// custom lookpath function so we know if a command is found *and* has execute permission
|
||||
func lookpath(file string) error {
|
||||
skip := []string{"./", "/", "../", "~/"}
|
||||
func lookpath(file string) error { // custom lookpath function so we know if a command is found *and* is executable
|
||||
var skip []string
|
||||
if runtime.GOOS == "windows" {
|
||||
skip = []string{"./", "../", "~/", "C:"}
|
||||
} else {
|
||||
skip = []string{"./", "/", "../", "~/"}
|
||||
}
|
||||
for _, s := range skip {
|
||||
if strings.HasPrefix(file, s) {
|
||||
err := findExecutable(file)
|
||||
return err
|
||||
return findExecutable(file, false, false)
|
||||
}
|
||||
}
|
||||
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
||||
path := filepath.Join(dir, file)
|
||||
err := findExecutable(path)
|
||||
if err == os.ErrPermission {
|
||||
err := findExecutable(path, true, false)
|
||||
if err == errNotExec {
|
||||
return err
|
||||
} else if err == nil {
|
||||
return nil
|
||||
|
@ -178,17 +399,6 @@ func lookpath(file string) error {
|
|||
return os.ErrNotExist
|
||||
}
|
||||
|
||||
func findExecutable(name string) error {
|
||||
f, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m := f.Mode(); !m.IsDir() && m & 0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
return os.ErrPermission
|
||||
}
|
||||
|
||||
func splitInput(input string) ([]string, string) {
|
||||
// end my suffering
|
||||
// TODO: refactor this garbage
|
||||
|
@ -242,11 +452,14 @@ func splitInput(input string) ([]string, string) {
|
|||
return cmdArgs, cmdstr.String()
|
||||
}
|
||||
|
||||
func cmdFinish(code uint8, cmdstr, oldInput string) {
|
||||
func cmdFinish(code uint8, cmdstr string, private bool) {
|
||||
// if input has space at the beginning, dont put in history
|
||||
if interactive && !strings.HasPrefix(oldInput, " ") {
|
||||
handleHistory(strings.TrimSpace(oldInput))
|
||||
if interactive && !private {
|
||||
handleHistory(cmdstr)
|
||||
}
|
||||
util.SetField(l, hshMod, "exitCode", lua.LNumber(code), "Exit code of last exected command")
|
||||
hooks.Em.Emit("command.exit", code, cmdstr)
|
||||
util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)), "Exit code of last exected command")
|
||||
// using AsValue (to convert to lua type) on an interface which is an int
|
||||
// results in it being unknown in lua .... ????
|
||||
// so we allow the hook handler to take lua runtime Values
|
||||
hooks.Em.Emit("command.exit", rt.IntValue(int64(code)), cmdstr)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
// +build linux darwin
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
func findExecutable(path string, inPath, dirs bool) error {
|
||||
f, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dirs {
|
||||
if m := f.Mode(); m & 0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if m := f.Mode(); !m.IsDir() && m & 0111 != 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errNotExec
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// +build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"os"
|
||||
)
|
||||
|
||||
func findExecutable(path string, inPath, dirs bool) error {
|
||||
nameExt := filepath.Ext(path)
|
||||
pathExts := filepath.SplitList(os.Getenv("PATHEXT"))
|
||||
if inPath {
|
||||
if nameExt == "" {
|
||||
for _, ext := range pathExts {
|
||||
_, err := os.Stat(path + ext)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
if contains(pathExts, nameExt) { return nil }
|
||||
return errNotExec
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
if contains(pathExts, nameExt) { return nil }
|
||||
return errNotExec
|
||||
}
|
||||
}
|
||||
|
||||
return os.ErrNotExist
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 99 KiB |
19
go.mod
19
go.mod
|
@ -1,21 +1,32 @@
|
|||
module hilbish
|
||||
|
||||
go 1.16
|
||||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86
|
||||
github.com/blackfireio/osinfo v1.0.3
|
||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9
|
||||
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036
|
||||
github.com/pborman/getopt v1.1.0
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
layeh.com/gopher-luar v1.0.10
|
||||
mvdan.cc/sh/v3 v3.4.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||
github.com/arnodel/strftime v0.1.6 // indirect
|
||||
github.com/evilsocket/islazy v1.10.6 // indirect
|
||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
|
||||
golang.org/x/text v0.3.6 // indirect
|
||||
)
|
||||
|
||||
replace mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e
|
||||
|
||||
replace github.com/maxlandon/readline => ./readline
|
||||
|
||||
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10
|
||||
|
||||
replace github.com/arnodel/golua => github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac
|
||||
|
|
41
go.sum
41
go.sum
|
@ -1,31 +1,24 @@
|
|||
github.com/Rosettea/readline-1 v0.0.0-20220302012429-9ce5d23760f7 h1:LoY+kBKqMQqBcilRpVvifBTVve84asa3btpx3D/+IvM=
|
||||
github.com/Rosettea/readline-1 v0.0.0-20220302012429-9ce5d23760f7/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
|
||||
github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119 h1:rGsc30WTD5hk+oiXrAKsAIwZn5qBeTAdr29y3HhJh9E=
|
||||
github.com/Rosettea/readline-1 v0.0.0-20220305004552-071c22768119/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
|
||||
github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93 h1:SmOkAEm3O7si8CURZSsSN0ZxCQ8IGiiulw8LMZ1V1Yc=
|
||||
github.com/Rosettea/readline-1 v0.0.0-20220305123014-31d4d4214c93/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
|
||||
github.com/Rosettea/readline-1 v0.1.0-beta.0.20211207003625-341c7985ad7d h1:KBttN41h/tPahmpaZavviwQ8q4rCkt5CD0HdVmfgPVA=
|
||||
github.com/Rosettea/readline-1 v0.1.0-beta.0.20211207003625-341c7985ad7d/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
|
||||
github.com/Rosettea/readline-1 v0.1.0-beta.0.20220228022904-61f5e4493011 h1:+a61iNamZiO3Xru+l/1qtpKqqltVfWEm2r/rxH9hXxY=
|
||||
github.com/Rosettea/readline-1 v0.1.0-beta.0.20220228022904-61f5e4493011/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
|
||||
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20211022004519-f67a49cb50f5 h1:ygwVRX8gf5MHA0VzSgOdscCEoAJLjM8joEotfQPgAd0=
|
||||
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20211022004519-f67a49cb50f5/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs=
|
||||
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac h1:dtXrgjch8PQyf7C90anZUquB5U3dr8AcMGJofeuirrI=
|
||||
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
|
||||
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e h1:P2XupP8SaylWaudD1DqbWtZ3mIa8OsE9635LmR+Q+lg=
|
||||
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/arnodel/edit v0.0.0-20220202110212-dfc8d7a13890/go.mod h1:AcpttpuZBaL9xl8/CX+Em4fBTUbwIkJ66RiAsJlNrBk=
|
||||
github.com/arnodel/strftime v0.1.6 h1:0hc0pUvk8KhEMXE+htyaOUV42zNcf/csIbjzEFCJqsw=
|
||||
github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4KTKzJfWs=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c=
|
||||
github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
|
||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
|
||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
|
||||
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/evilsocket/islazy v1.10.6 h1:MFq000a1ByoumoJWlytqg0qon0KlBeUfPsDjY0hK0bo=
|
||||
github.com/evilsocket/islazy v1.10.6/go.mod h1:OrwQGYg3DuZvXUfmH+KIZDjwTCbrjy48T24TUpGqVVw=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||
github.com/google/renameio v1.0.1/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
|
||||
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
|
@ -35,34 +28,36 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/layeh/gopher-luar v1.0.10 h1:8NIv4MX1Arz96kK4buGK1D87DyDxKZyq6KKvJ2diHp0=
|
||||
github.com/layeh/gopher-luar v1.0.10/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=
|
||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
|
||||
github.com/pborman/getopt v1.1.0 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
|
||||
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
|
||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210925032602-92d5a993a665/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7 h1:BXxu8t6QN0G1uff4bzZzSkpsax8+ALqTGUtz08QrV00=
|
||||
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
|
||||
|
|
|
@ -4,13 +4,14 @@ import (
|
|||
"fmt"
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
"github.com/chuckpreslar/emission"
|
||||
"github.com/yuin/gopher-lua"
|
||||
"layeh.com/gopher-luar"
|
||||
)
|
||||
|
||||
type Bait struct{
|
||||
Em *emission.Emitter
|
||||
Loader packagelib.Loader
|
||||
}
|
||||
|
||||
func New() Bait {
|
||||
|
@ -19,15 +20,27 @@ func New() Bait {
|
|||
emitter.Off(hookname, hookfunc)
|
||||
fmt.Println(err)
|
||||
})
|
||||
return Bait{
|
||||
b := Bait{
|
||||
Em: emitter,
|
||||
}
|
||||
b.Loader = packagelib.Loader{
|
||||
Load: b.loaderFunc,
|
||||
Name: "bait",
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bait) Loader(L *lua.LState) int {
|
||||
mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{})
|
||||
func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
exports := map[string]util.LuaExport{
|
||||
"catch": util.LuaExport{b.bcatch, 2, false},
|
||||
"catchOnce": util.LuaExport{b.bcatchOnce, 2, false},
|
||||
"throw": util.LuaExport{b.bthrow, 1, true},
|
||||
}
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
|
||||
util.Document(L, mod,
|
||||
util.Document(mod,
|
||||
`Bait is the event emitter for Hilbish. Why name it bait?
|
||||
Because it throws hooks that you can catch (emits events
|
||||
that you can listen to) and because why not, fun naming
|
||||
|
@ -36,35 +49,81 @@ in on hooks to know when certain things have happened,
|
|||
like when you've changed directory, a command has
|
||||
failed, etc. To find all available hooks, see doc hooks.`)
|
||||
|
||||
L.SetField(mod, "throw", luar.New(L, b.bthrow))
|
||||
L.SetField(mod, "catch", luar.New(L, b.bcatch))
|
||||
L.SetField(mod, "catchOnce", luar.New(L, b.bcatchOnce))
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
L.Push(mod)
|
||||
|
||||
return 1
|
||||
func handleHook(t *rt.Thread, c *rt.GoCont, name string, catcher *rt.Closure, args ...interface{}) {
|
||||
funcVal := rt.FunctionValue(catcher)
|
||||
var luaArgs []rt.Value
|
||||
for _, arg := range args {
|
||||
var luarg rt.Value
|
||||
switch arg.(type) {
|
||||
case rt.Value: luarg = arg.(rt.Value)
|
||||
default: luarg = rt.AsValue(arg)
|
||||
}
|
||||
luaArgs = append(luaArgs, luarg)
|
||||
}
|
||||
_, err := rt.Call1(t, funcVal, luaArgs...)
|
||||
if err != nil {
|
||||
e := rt.NewError(rt.StringValue(err.Error()))
|
||||
e = e.AddContext(c.Next(), 1)
|
||||
// panicking here won't actually cause hilbish to panic and instead will
|
||||
// print the error and remove the hook (look at emission recover from above)
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
|
||||
// throw(name, ...args)
|
||||
// Throws a hook with `name` with the provided `args`
|
||||
// --- @param name string
|
||||
// --- @vararg any
|
||||
func (b *Bait) bthrow(name string, args ...interface{}) {
|
||||
b.Em.Emit(name, args...)
|
||||
func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifaceSlice := make([]interface{}, len(c.Etc()))
|
||||
for i, v := range c.Etc() {
|
||||
ifaceSlice[i] = v
|
||||
}
|
||||
b.Em.Emit(name, ifaceSlice...)
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// catch(name, cb)
|
||||
// Catches a hook with `name`. Runs the `cb` when it is thrown
|
||||
// --- @param name string
|
||||
// --- @param cb function
|
||||
func (b *Bait) bcatch(name string, catcher func(...interface{})) {
|
||||
b.Em.On(name, catcher)
|
||||
func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
name, catcher, err := util.HandleStrCallback(t, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Em.On(name, func(args ...interface{}) {
|
||||
handleHook(t, c, name, catcher, args...)
|
||||
})
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// catchOnce(name, cb)
|
||||
// Same as catch, but only runs the `cb` once and then removes the hook
|
||||
// --- @param name string
|
||||
// --- @param cb function
|
||||
func (b *Bait) bcatchOnce(name string, catcher func(...interface{})) {
|
||||
b.Em.Once(name, catcher)
|
||||
func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
name, catcher, err := util.HandleStrCallback(t, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.Em.Once(name, func(args ...interface{}) {
|
||||
handleHook(t, c, name, catcher, args...)
|
||||
})
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
|
|
@ -3,52 +3,68 @@ package commander
|
|||
import (
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
"github.com/chuckpreslar/emission"
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
type Commander struct{
|
||||
Events *emission.Emitter
|
||||
Loader packagelib.Loader
|
||||
}
|
||||
|
||||
func New() Commander {
|
||||
return Commander{
|
||||
c := Commander{
|
||||
Events: emission.NewEmitter(),
|
||||
}
|
||||
c.Loader = packagelib.Loader{
|
||||
Load: c.loaderFunc,
|
||||
Name: "commander",
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Commander) Loader(L *lua.LState) int {
|
||||
exports := map[string]lua.LGFunction{
|
||||
"register": c.cregister,
|
||||
"deregister": c.cderegister,
|
||||
func (c *Commander) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
exports := map[string]util.LuaExport{
|
||||
"register": util.LuaExport{c.cregister, 2, false},
|
||||
"deregister": util.LuaExport{c.cderegister, 1, false},
|
||||
}
|
||||
mod := L.SetFuncs(L.NewTable(), exports)
|
||||
util.Document(L, mod, "Commander is Hilbish's custom command library, a way to write commands in Lua.")
|
||||
L.Push(mod)
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
util.Document(mod, "Commander is Hilbish's custom command library, a way to write commands in Lua.")
|
||||
|
||||
return 1
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
// register(name, cb)
|
||||
// Register a command with `name` that runs `cb` when ran
|
||||
// --- @param name string
|
||||
// --- @param cb function
|
||||
func (c *Commander) cregister(L *lua.LState) int {
|
||||
cmdName := L.CheckString(1)
|
||||
cmd := L.CheckFunction(2)
|
||||
func (c *Commander) cregister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
|
||||
cmdName, cmd, err := util.HandleStrCallback(t, ct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Events.Emit("commandRegister", cmdName, cmd)
|
||||
|
||||
return 0
|
||||
return ct.Next(), err
|
||||
}
|
||||
|
||||
// deregister(name)
|
||||
// Deregisters any command registered with `name`
|
||||
// --- @param name string
|
||||
func (c *Commander) cderegister(L *lua.LState) int {
|
||||
cmdName := L.CheckString(1)
|
||||
func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
|
||||
if err := ct.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmdName, err := ct.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.Events.Emit("commandDeregister", cmdName)
|
||||
|
||||
return 0
|
||||
return ct.Next(), err
|
||||
}
|
||||
|
|
124
golibs/fs/fs.go
124
golibs/fs/fs.go
|
@ -1,5 +1,3 @@
|
|||
// The fs module provides easy and simple access to filesystem functions and other
|
||||
// things, and acts an addition to the Lua standard library's I/O and fs functions.
|
||||
package fs
|
||||
|
||||
import (
|
||||
|
@ -8,51 +6,70 @@ import (
|
|||
"strings"
|
||||
|
||||
"hilbish/util"
|
||||
"github.com/yuin/gopher-lua"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
)
|
||||
|
||||
func Loader(L *lua.LState) int {
|
||||
mod := L.SetFuncs(L.NewTable(), exports)
|
||||
var Loader = packagelib.Loader{
|
||||
Load: loaderFunc,
|
||||
Name: "fs",
|
||||
}
|
||||
|
||||
util.Document(L, mod, `The fs module provides easy and simple access to
|
||||
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
exports := map[string]util.LuaExport{
|
||||
"cd": util.LuaExport{fcd, 1, false},
|
||||
"mkdir": util.LuaExport{fmkdir, 2, false},
|
||||
"stat": util.LuaExport{fstat, 1, false},
|
||||
"readdir": util.LuaExport{freaddir, 1, false},
|
||||
}
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
|
||||
util.Document(mod, `The fs module provides easy and simple access to
|
||||
filesystem functions and other things, and acts an
|
||||
addition to the Lua standard library's I/O and fs functions.`)
|
||||
|
||||
L.Push(mod)
|
||||
return 1
|
||||
}
|
||||
|
||||
var exports = map[string]lua.LGFunction{
|
||||
"cd": fcd,
|
||||
"mkdir": fmkdir,
|
||||
"stat": fstat,
|
||||
"readdir": freaddir,
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
// cd(dir)
|
||||
// Changes directory to `dir`
|
||||
// --- @param dir string
|
||||
func fcd(L *lua.LState) int {
|
||||
path := L.CheckString(1)
|
||||
|
||||
err := os.Chdir(strings.TrimSpace(path))
|
||||
func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
e := err.(*os.PathError).Err.Error()
|
||||
L.RaiseError(e + ": " + path)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return 0
|
||||
err = os.Chdir(strings.TrimSpace(path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Next(), err
|
||||
}
|
||||
|
||||
// mkdir(name, recursive)
|
||||
// Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
|
||||
// --- @param name string
|
||||
// --- @param recursive boolean
|
||||
func fmkdir(L *lua.LState) int {
|
||||
dirname := L.CheckString(1)
|
||||
recursive := L.ToBool(2)
|
||||
func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dirname, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recursive, err := c.BoolArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path := strings.TrimSpace(dirname)
|
||||
var err error
|
||||
|
||||
if recursive {
|
||||
err = os.MkdirAll(path, 0744)
|
||||
|
@ -60,51 +77,58 @@ func fmkdir(L *lua.LState) int {
|
|||
err = os.Mkdir(path, 0744)
|
||||
}
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error() + ": " + path)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return 0
|
||||
return c.Next(), err
|
||||
}
|
||||
|
||||
// stat(path)
|
||||
// Returns info about `path`
|
||||
// --- @param path string
|
||||
func fstat(L *lua.LState) int {
|
||||
path := L.CheckString(1)
|
||||
func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
path, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pathinfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error() + ": " + path)
|
||||
return 0
|
||||
return nil, err
|
||||
}
|
||||
statTbl := L.NewTable()
|
||||
L.SetField(statTbl, "name", lua.LString(pathinfo.Name()))
|
||||
L.SetField(statTbl, "size", lua.LNumber(pathinfo.Size()))
|
||||
L.SetField(statTbl, "mode", lua.LString("0" + strconv.FormatInt(int64(pathinfo.Mode().Perm()), 8)))
|
||||
L.SetField(statTbl, "isDir", lua.LBool(pathinfo.IsDir()))
|
||||
L.Push(statTbl)
|
||||
|
||||
return 1
|
||||
statTbl := rt.NewTable()
|
||||
statTbl.Set(rt.StringValue("name"), rt.StringValue(pathinfo.Name()))
|
||||
statTbl.Set(rt.StringValue("size"), rt.IntValue(pathinfo.Size()))
|
||||
statTbl.Set(rt.StringValue("mode"), rt.StringValue("0" + strconv.FormatInt(int64(pathinfo.Mode().Perm()), 8)))
|
||||
statTbl.Set(rt.StringValue("isDir"), rt.BoolValue(pathinfo.IsDir()))
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), nil
|
||||
}
|
||||
|
||||
// readdir(dir)
|
||||
// Returns a table of files in `dir`
|
||||
// --- @param dir string
|
||||
// --- @return table
|
||||
func freaddir(L *lua.LState) int {
|
||||
dir := L.CheckString(1)
|
||||
names := L.NewTable()
|
||||
func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dir, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names := rt.NewTable()
|
||||
|
||||
dirEntries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error() + ": " + dir)
|
||||
return 0
|
||||
return nil, err
|
||||
}
|
||||
for _, entry := range dirEntries {
|
||||
names.Append(lua.LString(entry.Name()))
|
||||
for i, entry := range dirEntries {
|
||||
names.Set(rt.IntValue(int64(i + 1)), rt.StringValue(entry.Name()))
|
||||
}
|
||||
|
||||
L.Push(names)
|
||||
|
||||
return 1
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(names)), nil
|
||||
}
|
||||
|
|
|
@ -5,76 +5,78 @@ import (
|
|||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib/packagelib"
|
||||
"golang.org/x/term"
|
||||
"github.com/yuin/gopher-lua"
|
||||
)
|
||||
|
||||
var termState *term.State
|
||||
|
||||
func Loader(L *lua.LState) int {
|
||||
mod := L.SetFuncs(L.NewTable(), exports)
|
||||
util.Document(L, mod, "The terminal library is a simple and lower level library for certain terminal interactions.")
|
||||
|
||||
L.Push(mod)
|
||||
|
||||
return 1
|
||||
var Loader = packagelib.Loader{
|
||||
Load: loaderFunc,
|
||||
Name: "terminal",
|
||||
}
|
||||
|
||||
var exports = map[string]lua.LGFunction{
|
||||
"setRaw": termraw,
|
||||
"restoreState": termrestoreState,
|
||||
"size": termsize,
|
||||
"saveState": termsaveState,
|
||||
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||
exports := map[string]util.LuaExport{
|
||||
"setRaw": util.LuaExport{termsetRaw, 0, false},
|
||||
"restoreState": util.LuaExport{termrestoreState, 0, false},
|
||||
"size": util.LuaExport{termsize, 0, false},
|
||||
"saveState": util.LuaExport{termsaveState, 0, false},
|
||||
}
|
||||
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
util.Document(mod, "The terminal library is a simple and lower level library for certain terminal interactions.")
|
||||
|
||||
return rt.TableValue(mod), nil
|
||||
}
|
||||
|
||||
// size()
|
||||
// Gets the dimensions of the terminal. Returns a table with `width` and `height`
|
||||
// Note: this is not the size in relation to the dimensions of the display
|
||||
func termsize(L *lua.LState) int {
|
||||
func termsize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
w, h, err := term.GetSize(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
return 0
|
||||
return nil, err
|
||||
}
|
||||
dimensions := L.NewTable()
|
||||
L.SetField(dimensions, "width", lua.LNumber(w))
|
||||
L.SetField(dimensions, "height", lua.LNumber(h))
|
||||
|
||||
L.Push(dimensions)
|
||||
return 1
|
||||
dimensions := rt.NewTable()
|
||||
dimensions.Set(rt.StringValue("width"), rt.IntValue(int64(w)))
|
||||
dimensions.Set(rt.StringValue("height"), rt.IntValue(int64(h)))
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(dimensions)), nil
|
||||
}
|
||||
|
||||
// saveState()
|
||||
// Saves the current state of the terminal
|
||||
func termsaveState(L *lua.LState) int {
|
||||
func termsaveState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
state, err := term.GetState(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
return 0
|
||||
return nil, err
|
||||
}
|
||||
|
||||
termState = state
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// restoreState()
|
||||
// Restores the last saved state of the terminal
|
||||
func termrestoreState(L *lua.LState) int {
|
||||
func termrestoreState(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
err := term.Restore(int(os.Stdin.Fd()), termState)
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
// setRaw()
|
||||
// Puts the terminal in raw mode
|
||||
func termraw(L *lua.LState) int {
|
||||
func termsetRaw(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
_, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
L.RaiseError(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
|
|
@ -78,3 +78,9 @@ func (h *fileHistory) Len() int {
|
|||
func (h *fileHistory) Dump() interface{} {
|
||||
return h.items
|
||||
}
|
||||
|
||||
func (h *fileHistory) clear() {
|
||||
h.items = []string{}
|
||||
h.f.Truncate(0)
|
||||
h.f.Sync()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"os"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var jobs *jobHandler
|
||||
|
||||
type job struct {
|
||||
cmd string
|
||||
running bool
|
||||
id int
|
||||
pid int
|
||||
exitCode int
|
||||
proc *os.Process
|
||||
}
|
||||
|
||||
func (j *job) start(pid int) {
|
||||
j.pid = pid
|
||||
j.running = true
|
||||
hooks.Em.Emit("job.start", j.lua())
|
||||
}
|
||||
|
||||
func (j *job) stop() {
|
||||
// finish will be called in exec handle
|
||||
j.proc.Kill()
|
||||
}
|
||||
|
||||
func (j *job) finish() {
|
||||
j.running = false
|
||||
hooks.Em.Emit("job.done", j.lua())
|
||||
}
|
||||
|
||||
func (j *job) setHandle(handle *os.Process) {
|
||||
j.proc = handle
|
||||
}
|
||||
|
||||
func (j *job) lua() rt.Value {
|
||||
jobFuncs := map[string]util.LuaExport{
|
||||
"stop": {j.luaStop, 0, false},
|
||||
}
|
||||
luaJob := rt.NewTable()
|
||||
util.SetExports(l, luaJob, jobFuncs)
|
||||
|
||||
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)))
|
||||
|
||||
return rt.TableValue(luaJob)
|
||||
}
|
||||
|
||||
func (j *job) luaStop(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if j.running {
|
||||
j.stop()
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
type jobHandler struct {
|
||||
jobs map[int]*job
|
||||
latestID int
|
||||
mu *sync.RWMutex
|
||||
}
|
||||
|
||||
func newJobHandler() *jobHandler {
|
||||
return &jobHandler{
|
||||
jobs: make(map[int]*job),
|
||||
latestID: 0,
|
||||
mu: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jobHandler) add(cmd string) {
|
||||
j.mu.Lock()
|
||||
defer j.mu.Unlock()
|
||||
|
||||
j.latestID++
|
||||
j.jobs[j.latestID] = &job{
|
||||
cmd: cmd,
|
||||
running: false,
|
||||
id: j.latestID,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *jobHandler) getLatest() *job {
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
|
||||
return j.jobs[j.latestID]
|
||||
}
|
||||
|
||||
func (j *jobHandler) loader(rtm *rt.Runtime) *rt.Table {
|
||||
jobFuncs := map[string]util.LuaExport{
|
||||
"all": {j.luaAllJobs, 0, false},
|
||||
"get": {j.luaGetJob, 1, false},
|
||||
}
|
||||
|
||||
luaJob := rt.NewTable()
|
||||
util.SetExports(rtm, luaJob, jobFuncs)
|
||||
|
||||
return luaJob
|
||||
}
|
||||
|
||||
func (j *jobHandler) luaGetJob(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
jobID, err := c.IntArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
job := j.jobs[int(jobID)]
|
||||
if job == nil {
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
return c.PushingNext1(t.Runtime, job.lua()), nil
|
||||
}
|
||||
|
||||
func (j *jobHandler) luaAllJobs(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
|
||||
jobTbl := rt.NewTable()
|
||||
for id, job := range j.jobs {
|
||||
jobTbl.Set(rt.IntValue(int64(id)), job.lua())
|
||||
}
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(jobTbl)), nil
|
||||
}
|
|
@ -89,21 +89,25 @@ end
|
|||
|
||||
ansikit.print = function(text)
|
||||
io.write(ansikit.format(text))
|
||||
io.flush()
|
||||
return ansikit
|
||||
end
|
||||
|
||||
ansikit.printCode = function(code, terminate)
|
||||
io.write(ansikit.getCode(code, terminate))
|
||||
io.flush()
|
||||
return ansikit
|
||||
end
|
||||
|
||||
ansikit.printCSI = function(code, endc)
|
||||
io.write(ansikit.getCSI(code, endc))
|
||||
io.flush()
|
||||
return ansikit
|
||||
end
|
||||
|
||||
ansikit.println = function(text)
|
||||
print(ansikit.print(text))
|
||||
io.write(ansikit.format(text) .. "\n")
|
||||
io.flush()
|
||||
return ansikit
|
||||
end
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 5a59d0f4543eb982593750c52f7393e2fd2d15f9
|
||||
Subproject commit b362397a83e4516415c809c7d690b52e79a95f6e
|
47
lua.go
47
lua.go
|
@ -4,40 +4,42 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"hilbish/util"
|
||||
"hilbish/golibs/bait"
|
||||
"hilbish/golibs/commander"
|
||||
"hilbish/golibs/fs"
|
||||
"hilbish/golibs/terminal"
|
||||
|
||||
"github.com/yuin/gopher-lua"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/arnodel/golua/lib"
|
||||
)
|
||||
|
||||
var minimalconf = `hilbish.prompt '& '`
|
||||
|
||||
func luaInit() {
|
||||
l = lua.NewState()
|
||||
l.OpenLibs()
|
||||
l = rt.New(os.Stdout)
|
||||
lib.LoadAll(l)
|
||||
|
||||
lib.LoadLibs(l, hilbishLoader)
|
||||
// yes this is stupid, i know
|
||||
l.PreloadModule("hilbish", hilbishLoader)
|
||||
l.DoString("hilbish = require 'hilbish'")
|
||||
util.DoString(l, "hilbish = require 'hilbish'")
|
||||
|
||||
// Add fs and terminal module module to Lua
|
||||
l.PreloadModule("fs", fs.Loader)
|
||||
l.PreloadModule("terminal", terminal.Loader)
|
||||
lib.LoadLibs(l, fs.Loader)
|
||||
lib.LoadLibs(l, terminal.Loader)
|
||||
|
||||
cmds := commander.New()
|
||||
// When a command from Lua is added, register it for use
|
||||
cmds.Events.On("commandRegister", func(cmdName string, cmd *lua.LFunction) {
|
||||
cmds.Events.On("commandRegister", func(cmdName string, cmd *rt.Closure) {
|
||||
commands[cmdName] = cmd
|
||||
})
|
||||
cmds.Events.On("commandDeregister", func(cmdName string) {
|
||||
delete(commands, cmdName)
|
||||
})
|
||||
l.PreloadModule("commander", cmds.Loader)
|
||||
lib.LoadLibs(l, cmds.Loader)
|
||||
|
||||
hooks = bait.New()
|
||||
l.PreloadModule("bait", hooks.Loader)
|
||||
lib.LoadLibs(l, hooks.Loader)
|
||||
|
||||
// Add Ctrl-C handler
|
||||
hooks.Em.On("signal.sigint", func() {
|
||||
|
@ -46,29 +48,28 @@ func luaInit() {
|
|||
}
|
||||
})
|
||||
|
||||
l.SetGlobal("complete", l.NewFunction(hlcomplete))
|
||||
|
||||
// Add more paths that Lua can require from
|
||||
l.DoString("package.path = package.path .. " + requirePaths)
|
||||
|
||||
err := l.DoFile("prelude/init.lua")
|
||||
err := util.DoString(l, "package.path = package.path .. " + requirePaths)
|
||||
if err != nil {
|
||||
err = l.DoFile(preloadPath)
|
||||
fmt.Fprintln(os.Stderr, "Could not add preload paths! Libraries will be missing. This shouldn't happen.")
|
||||
}
|
||||
|
||||
err = util.DoFile(l, "prelude/init.lua")
|
||||
if err != nil {
|
||||
err = util.DoFile(l, preloadPath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr,
|
||||
"Missing preload file, builtins may be missing.")
|
||||
fmt.Fprintln(os.Stderr, "Missing preload file, builtins may be missing.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runConfig(confpath string) {
|
||||
if !interactive {
|
||||
return
|
||||
}
|
||||
err := l.DoFile(confpath)
|
||||
err := util.DoFile(l, confpath)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err,
|
||||
"\nAn error has occured while loading your config! Falling back to minimal default config.")
|
||||
|
||||
l.DoString(minimalconf)
|
||||
fmt.Fprintln(os.Stderr, err, "\nAn error has occured while loading your config! Falling back to minimal default config.")
|
||||
util.DoString(l, minimalconf)
|
||||
}
|
||||
}
|
||||
|
|
62
main.go
62
main.go
|
@ -10,20 +10,21 @@ import (
|
|||
"runtime"
|
||||
"strings"
|
||||
|
||||
"hilbish/util"
|
||||
"hilbish/golibs/bait"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
"github.com/pborman/getopt"
|
||||
"github.com/yuin/gopher-lua"
|
||||
"github.com/maxlandon/readline"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
l *lua.LState
|
||||
l *rt.Runtime
|
||||
lr *lineReader
|
||||
|
||||
commands = map[string]*lua.LFunction{}
|
||||
luaCompletions = map[string]*lua.LFunction{}
|
||||
commands = map[string]*rt.Closure{}
|
||||
luaCompletions = map[string]*rt.Closure{}
|
||||
|
||||
confDir string
|
||||
userDataDir string
|
||||
|
@ -43,7 +44,7 @@ func main() {
|
|||
|
||||
// i honestly dont know what directories to use for this
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
case "linux", "darwin":
|
||||
userDataDir = getenv("XDG_DATA_HOME", curuser.HomeDir + "/.local/share")
|
||||
default:
|
||||
// this is fine on windows, dont know about others
|
||||
|
@ -55,7 +56,7 @@ func main() {
|
|||
defaultConfDir = filepath.Join(confDir, "hilbish")
|
||||
} else {
|
||||
// else do ~ substitution
|
||||
defaultConfDir = expandHome(defaultHistDir)
|
||||
defaultConfDir = filepath.Join(expandHome(defaultConfDir), "hilbish")
|
||||
}
|
||||
defaultConfPath = filepath.Join(defaultConfDir, "init.lua")
|
||||
if defaultHistDir == "" {
|
||||
|
@ -142,27 +143,28 @@ func main() {
|
|||
scanner := bufio.NewScanner(bufio.NewReader(os.Stdin))
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
runInput(text, text)
|
||||
runInput(text, true)
|
||||
}
|
||||
exit(0)
|
||||
}
|
||||
|
||||
if *cmdflag != "" {
|
||||
runInput(*cmdflag, *cmdflag)
|
||||
runInput(*cmdflag, true)
|
||||
}
|
||||
|
||||
if getopt.NArgs() > 0 {
|
||||
luaArgs := l.NewTable()
|
||||
for _, arg := range getopt.Args() {
|
||||
luaArgs.Append(lua.LString(arg))
|
||||
luaArgs := rt.NewTable()
|
||||
for i, arg := range getopt.Args() {
|
||||
luaArgs.Set(rt.IntValue(int64(i)), rt.StringValue(arg))
|
||||
}
|
||||
|
||||
l.SetGlobal("args", luaArgs)
|
||||
err := l.DoFile(getopt.Arg(0))
|
||||
l.GlobalEnv().Set(rt.StringValue("args"), rt.TableValue(luaArgs))
|
||||
err := util.DoFile(l, getopt.Arg(0))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
exit(0)
|
||||
}
|
||||
|
||||
initialized = true
|
||||
|
@ -185,7 +187,10 @@ input:
|
|||
fmt.Println("^C")
|
||||
continue
|
||||
}
|
||||
oldInput := input
|
||||
var priv bool
|
||||
if strings.HasPrefix(input, " ") {
|
||||
priv = true
|
||||
}
|
||||
|
||||
input = strings.TrimSpace(input)
|
||||
if len(input) == 0 {
|
||||
|
@ -198,6 +203,8 @@ input:
|
|||
for {
|
||||
input, err = continuePrompt(input)
|
||||
if err != nil {
|
||||
running = true
|
||||
lr.SetPrompt(fmtPrompt(prompt))
|
||||
goto input // continue inside nested loop
|
||||
}
|
||||
if !strings.HasSuffix(input, "\\") {
|
||||
|
@ -206,7 +213,7 @@ input:
|
|||
}
|
||||
}
|
||||
|
||||
runInput(input, oldInput)
|
||||
runInput(input, priv)
|
||||
|
||||
termwidth, _, err := term.GetSize(0)
|
||||
if err != nil {
|
||||
|
@ -268,8 +275,7 @@ func handleHistory(cmd string) {
|
|||
|
||||
func expandHome(path string) string {
|
||||
homedir := curuser.HomeDir
|
||||
|
||||
return strings.Replace(defaultHistDir, "~", homedir, 1)
|
||||
return strings.Replace(path, "~", homedir, 1)
|
||||
}
|
||||
|
||||
func removeDupes(slice []string) []string {
|
||||
|
@ -284,3 +290,21 @@ func removeDupes(slice []string) []string {
|
|||
|
||||
return newSlice
|
||||
}
|
||||
|
||||
func contains(s []string, e string) bool {
|
||||
for _, a := range s {
|
||||
if a == e {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func exit(code int) {
|
||||
// wait for all timers to finish before exiting
|
||||
for {
|
||||
if timers.running == 0 {
|
||||
os.Exit(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ local _ = require 'succulent' -- Function additions
|
|||
local oldDir = hilbish.cwd()
|
||||
|
||||
local shlvl = tonumber(os.getenv 'SHLVL')
|
||||
if shlvl ~= nil then os.setenv('SHLVL', shlvl + 1) else os.setenv('SHLVL', 0) end
|
||||
if shlvl ~= nil then os.setenv('SHLVL', tostring(shlvl + 1)) else os.setenv('SHLVL', '0') end
|
||||
|
||||
-- Builtins
|
||||
local recentDirs = {}
|
||||
|
@ -168,6 +168,9 @@ hilbish.userDir.config .. '/hilbish/init.lua' ..
|
|||
and also change all global functions (prompt, alias) to be
|
||||
in the hilbish module (hilbish.prompt, hilbish.alias as examples).
|
||||
|
||||
And if this is your first time (most likely), you can copy a config
|
||||
from ]] .. hilbish.dataDir,
|
||||
[[
|
||||
Since 1.0 is a big release, you'll want to check the changelog
|
||||
at https://github.com/Rosettea/Hilbish/releases/tag/v1.0.0
|
||||
to find more breaking changes.
|
||||
|
@ -214,14 +217,6 @@ do
|
|||
end
|
||||
end,
|
||||
})
|
||||
|
||||
bait.catch('command.exit', function ()
|
||||
for key, value in pairs(virt_G) do
|
||||
if type(value) == 'string' then
|
||||
virt_G[key] = os.getenv(key)
|
||||
end
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
commander.register('cdr', function(args)
|
||||
|
@ -263,7 +258,7 @@ bait.catch('command.not-found', function(cmd)
|
|||
print(string.format('hilbish: %s not found', cmd))
|
||||
end)
|
||||
|
||||
bait.catch('command.no-perm', function(cmd)
|
||||
print(string.format('hilbish: %s: no permission', cmd))
|
||||
bait.catch('command.not-executable', function(cmd)
|
||||
print(string.format('hilbish: %s: not executable', cmd))
|
||||
end)
|
||||
|
||||
|
|
|
@ -34,32 +34,39 @@ const (
|
|||
charCtrlHat // ^^
|
||||
charCtrlUnderscore // ^_
|
||||
charBackspace2 = 127 // ASCII 1963
|
||||
|
||||
)
|
||||
|
||||
// Escape sequences
|
||||
var (
|
||||
seqUp = string([]byte{27, 91, 65})
|
||||
seqDown = string([]byte{27, 91, 66})
|
||||
seqForwards = string([]byte{27, 91, 67})
|
||||
seqBackwards = string([]byte{27, 91, 68})
|
||||
seqHome = string([]byte{27, 91, 72})
|
||||
seqHomeSc = string([]byte{27, 91, 49, 126})
|
||||
seqEnd = string([]byte{27, 91, 70})
|
||||
seqEndSc = string([]byte{27, 91, 52, 126})
|
||||
seqDelete = string([]byte{27, 91, 51, 126})
|
||||
seqShiftTab = string([]byte{27, 91, 90})
|
||||
seqAltQuote = string([]byte{27, 34}) // Added for showing registers ^["
|
||||
seqAltR = string([]byte{27, 114}) // Used for alternative history
|
||||
seqUp = string([]byte{27, 91, 65})
|
||||
seqDown = string([]byte{27, 91, 66})
|
||||
seqForwards = string([]byte{27, 91, 67})
|
||||
seqBackwards = string([]byte{27, 91, 68})
|
||||
seqHome = string([]byte{27, 91, 72})
|
||||
seqHomeSc = string([]byte{27, 91, 49, 126})
|
||||
seqEnd = string([]byte{27, 91, 70})
|
||||
seqEndSc = string([]byte{27, 91, 52, 126})
|
||||
seqDelete = string([]byte{27, 91, 51, 126})
|
||||
seqDelete2 = string([]byte{27, 91, 80})
|
||||
seqCtrlDelete = string([]byte{27, 91, 51, 59, 53, 126})
|
||||
seqCtrlDelete2 = string([]byte{27, 91, 77})
|
||||
seqAltDelete = string([]byte{27, 91, 51, 59, 51, 126})
|
||||
seqShiftTab = string([]byte{27, 91, 90})
|
||||
seqAltQuote = string([]byte{27, 34}) // Added for showing registers ^["
|
||||
seqAltB = string([]byte{27, 98})
|
||||
seqAltD = string([]byte{27, 100})
|
||||
seqAltF = string([]byte{27, 102})
|
||||
seqAltR = string([]byte{27, 114}) // Used for alternative history
|
||||
seqAltBackspace = string([]byte{27, 127})
|
||||
)
|
||||
|
||||
const (
|
||||
seqPosSave = "\x1b[s"
|
||||
seqPosRestore = "\x1b[u"
|
||||
|
||||
seqClearLineAfer = "\x1b[0k"
|
||||
seqClearLineBefore = "\x1b[1k"
|
||||
seqClearLine = "\x1b[2k"
|
||||
seqClearLineAfer = "\x1b[0K"
|
||||
seqClearLineBefore = "\x1b[1K"
|
||||
seqClearLine = "\x1b[2K"
|
||||
seqClearScreenBelow = "\x1b[0J"
|
||||
seqClearScreen = "\x1b[2J" // Clears screen fully
|
||||
seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left
|
||||
|
@ -78,6 +85,7 @@ const (
|
|||
seqBold = "\x1b[1m"
|
||||
seqUnderscore = "\x1b[4m"
|
||||
seqBlink = "\x1b[5m"
|
||||
seqInvert = "\x1b[7m"
|
||||
)
|
||||
|
||||
// Text colours
|
||||
|
|
|
@ -121,7 +121,7 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
|||
}
|
||||
|
||||
if (x == g.tcPosX && y == g.tcPosY) && (g.isCurrent) {
|
||||
comp += seqCtermFg255 + seqFgBlackBright
|
||||
comp += seqInvert
|
||||
}
|
||||
|
||||
comp += fmt.Sprintf("%-"+cellWidth+"s %s", g.Suggestions[i], seqReset)
|
||||
|
|
|
@ -188,7 +188,7 @@ func (g *CompletionGroup) writeList(rl *Instance) (comp string) {
|
|||
// function highlights the cell depending on current selector place.
|
||||
highlight := func(y int, x int) string {
|
||||
if y == g.tcPosY && x == g.tcPosX && g.isCurrent {
|
||||
return seqCtermFg255 + seqFgBlackBright
|
||||
return seqInvert
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ func (g *CompletionGroup) writeMap(rl *Instance) (comp string) {
|
|||
// Highlighting function
|
||||
highlight := func(y int) string {
|
||||
if y == g.tcPosY && g.isCurrent {
|
||||
return seqCtermFg255 + seqFgBlackBright
|
||||
return seqInvert
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -100,12 +100,12 @@ func moveCursorBackwards(i int) {
|
|||
printf("\x1b[%dD", i)
|
||||
}
|
||||
|
||||
func (rl *Instance) backspace() {
|
||||
func (rl *Instance) backspace(forward bool) {
|
||||
if len(rl.line) == 0 || rl.pos == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
rl.deleteBackspace()
|
||||
rl.deleteBackspace(forward)
|
||||
}
|
||||
|
||||
func (rl *Instance) moveCursorByAdjust(adjust int) {
|
||||
|
|
|
@ -7,7 +7,7 @@ type EventReturn struct {
|
|||
ForwardKey bool
|
||||
ClearHelpers bool
|
||||
CloseReadline bool
|
||||
HintText []rune
|
||||
InfoText []rune
|
||||
NewLine []rune
|
||||
NewPos int
|
||||
}
|
||||
|
|
|
@ -4,10 +4,12 @@ import "regexp"
|
|||
|
||||
// SetHintText - a nasty function to force writing a new hint text. It does not update helpers, it just renders
|
||||
// them, so the hint will survive until the helpers (thus including the hint) will be updated/recomputed.
|
||||
/*
|
||||
func (rl *Instance) SetHintText(s string) {
|
||||
rl.hintText = []rune(s)
|
||||
rl.renderHelpers()
|
||||
}
|
||||
*/
|
||||
|
||||
func (rl *Instance) getHintText() {
|
||||
|
||||
|
@ -27,7 +29,7 @@ func (rl *Instance) getHintText() {
|
|||
// writeHintText - only writes the hint text and computes its offsets.
|
||||
func (rl *Instance) writeHintText() {
|
||||
if len(rl.hintText) == 0 {
|
||||
rl.hintY = 0
|
||||
//rl.hintY = 0
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -41,16 +43,16 @@ func (rl *Instance) writeHintText() {
|
|||
|
||||
wrapped, hintLen := WrapText(string(rl.hintText), width)
|
||||
offset += hintLen
|
||||
rl.hintY = offset
|
||||
// rl.hintY = offset
|
||||
|
||||
hintText := string(wrapped)
|
||||
|
||||
if len(hintText) > 0 {
|
||||
print("\r" + rl.HintFormatting + string(hintText) + seqReset)
|
||||
print(rl.HintFormatting + string(hintText) + seqReset)
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) resetHintText() {
|
||||
rl.hintY = 0
|
||||
//rl.hintY = 0
|
||||
rl.hintText = []rune{}
|
||||
}
|
||||
|
|
|
@ -183,13 +183,13 @@ func (rl *Instance) completeHistory() (hist []*CompletionGroup) {
|
|||
return
|
||||
}
|
||||
history = rl.altHistory
|
||||
rl.histHint = []rune(rl.altHistName + ": ")
|
||||
rl.histInfo = []rune(rl.altHistName + ": ")
|
||||
} else {
|
||||
if rl.mainHistory == nil {
|
||||
return
|
||||
}
|
||||
history = rl.mainHistory
|
||||
rl.histHint = []rune(rl.mainHistName + ": ")
|
||||
rl.histInfo = []rune(rl.mainHistName + ": ")
|
||||
}
|
||||
|
||||
hist[0].init(rl)
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package readline
|
||||
|
||||
import "regexp"
|
||||
|
||||
// SetInfoText - a nasty function to force writing a new info text. It does not update helpers, it just renders
|
||||
// them, so the info will survive until the helpers (thus including the info) will be updated/recomputed.
|
||||
func (rl *Instance) SetInfoText(s string) {
|
||||
rl.infoText = []rune(s)
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) getInfoText() {
|
||||
|
||||
if !rl.modeAutoFind && !rl.modeTabFind {
|
||||
// Return if no infos provided by the user/engine
|
||||
if rl.InfoText == nil {
|
||||
rl.resetInfoText()
|
||||
return
|
||||
}
|
||||
// The info text also works with the virtual completion line system.
|
||||
// This way, the info is also refreshed depending on what we are pointing
|
||||
// at with our cursor.
|
||||
rl.infoText = rl.InfoText(rl.getCompletionLine())
|
||||
}
|
||||
}
|
||||
|
||||
// writeInfoText - only writes the info text and computes its offsets.
|
||||
func (rl *Instance) writeInfoText() {
|
||||
if len(rl.infoText) == 0 {
|
||||
rl.infoY = 0
|
||||
return
|
||||
}
|
||||
|
||||
width := GetTermWidth()
|
||||
|
||||
// Wraps the line, and counts the number of newlines in the string,
|
||||
// adjusting the offset as well.
|
||||
re := regexp.MustCompile(`\r?\n`)
|
||||
newlines := re.Split(string(rl.infoText), -1)
|
||||
offset := len(newlines)
|
||||
|
||||
wrapped, infoLen := WrapText(string(rl.infoText), width)
|
||||
offset += infoLen
|
||||
rl.infoY = offset
|
||||
|
||||
infoText := string(wrapped)
|
||||
|
||||
if len(infoText) > 0 {
|
||||
print("\r" + rl.InfoFormatting + string(infoText) + seqReset)
|
||||
}
|
||||
}
|
||||
|
||||
func (rl *Instance) resetInfoText() {
|
||||
rl.infoY = 0
|
||||
rl.infoText = []rune{}
|
||||
}
|
|
@ -30,11 +30,13 @@ type Instance struct {
|
|||
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.
|
||||
|
||||
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.
|
||||
defaultPrompt []rune
|
||||
promptLen int
|
||||
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
||||
mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt
|
||||
rightPrompt string
|
||||
rightPromptLen int
|
||||
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
|
||||
defaultPrompt []rune
|
||||
promptLen int
|
||||
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
||||
|
||||
//
|
||||
// Input Line ---------------------------------------------------------------------------------
|
||||
|
@ -110,7 +112,7 @@ type Instance struct {
|
|||
searchMode FindMode // Used for varying hints, and underlying functions called
|
||||
regexSearch *regexp.Regexp // Holds the current search regex match
|
||||
mainHist bool // Which history stdin do we want
|
||||
histHint []rune // We store a hist hint, for dual history sources
|
||||
histInfo []rune // We store a piece of hist info, for dual history sources
|
||||
|
||||
//
|
||||
// History -----------------------------------------------------------------------------------
|
||||
|
@ -134,19 +136,33 @@ type Instance struct {
|
|||
histNavIdx int // Used for quick history navigation.
|
||||
|
||||
//
|
||||
// Hints -------------------------------------------------------------------------------------
|
||||
// Info -------------------------------------------------------------------------------------
|
||||
|
||||
// HintText is a helper function which displays hint text the prompt.
|
||||
// HintText takes the line input from the promt and the cursor position.
|
||||
// InfoText is a helper function which displays infio text below the prompt.
|
||||
// InfoText takes the line input from the prompt and the cursor position.
|
||||
// It returns the info text to display.
|
||||
InfoText func([]rune, int) []rune
|
||||
|
||||
// InfoColor is any ANSI escape codes you wish to use for info formatting. By
|
||||
// default this will just be blue.
|
||||
InfoFormatting string
|
||||
|
||||
infoText []rune // The actual info text
|
||||
infoY int // Offset to info, if it spans multiple lines
|
||||
|
||||
//
|
||||
// Hints -----------------------------------------------------------------------------------
|
||||
|
||||
// HintText is a helper function which displays hint text right after the user's input.
|
||||
// It takes the line input and cursor position.
|
||||
// It returns the hint text to display.
|
||||
HintText func([]rune, int) []rune
|
||||
|
||||
// HintColor any ANSI escape codes you wish to use for hint formatting. By
|
||||
// default this will just be blue.
|
||||
// HintFormatting is just a string to use as the formatting for the hint. By default
|
||||
// this will be a grey color.
|
||||
HintFormatting string
|
||||
|
||||
hintText []rune // The actual hint text
|
||||
hintY int // Offset to hints, if it spans multiple lines
|
||||
hintText []rune
|
||||
|
||||
//
|
||||
// Vim Operatng Parameters -------------------------------------------------------------------
|
||||
|
@ -205,7 +221,8 @@ func NewInstance() *Instance {
|
|||
rl.HistoryAutoWrite = true
|
||||
|
||||
// Others
|
||||
rl.HintFormatting = seqFgBlue
|
||||
rl.InfoFormatting = seqFgBlue
|
||||
rl.HintFormatting = "\x1b[2m"
|
||||
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
||||
rl.TempDirectory = os.TempDir()
|
||||
|
||||
|
|
|
@ -57,9 +57,9 @@ func (rl *Instance) echo() {
|
|||
|
||||
// Print the input line with optional syntax highlighting
|
||||
if rl.SyntaxHighlighter != nil {
|
||||
print(rl.SyntaxHighlighter(line) + " ")
|
||||
print(rl.SyntaxHighlighter(line))
|
||||
} else {
|
||||
print(string(line) + " ")
|
||||
print(string(line))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,14 +125,14 @@ func (rl *Instance) deleteX() {
|
|||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteBackspace() {
|
||||
func (rl *Instance) deleteBackspace(forward bool) {
|
||||
switch {
|
||||
case len(rl.line) == 0:
|
||||
return
|
||||
case rl.pos == 0:
|
||||
rl.line = rl.line[1:]
|
||||
case forward:
|
||||
rl.line = append(rl.line[:rl.pos], rl.line[rl.pos+1:]...)
|
||||
case rl.pos > len(rl.line):
|
||||
rl.backspace() // There is an infite loop going on here...
|
||||
rl.backspace(forward) // There is an infite loop going on here...
|
||||
case rl.pos == len(rl.line):
|
||||
rl.pos--
|
||||
rl.line = rl.line[:rl.pos]
|
||||
|
@ -176,3 +176,48 @@ func (rl *Instance) deleteToBeginning() {
|
|||
rl.line = rl.line[rl.pos:]
|
||||
rl.pos = 0
|
||||
}
|
||||
|
||||
func (rl *Instance) deleteToEnd() {
|
||||
rl.resetVirtualComp(false)
|
||||
// Keep everything before the cursor
|
||||
rl.line = rl.line[:rl.pos]
|
||||
}
|
||||
|
||||
// @TODO(Renzix): move to emacs sepecific file
|
||||
func (rl *Instance) emacsForwardWord(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
word := strings.TrimSpace(split[index])
|
||||
|
||||
switch {
|
||||
case len(split) == 0:
|
||||
return
|
||||
case pos == len(word) && index != len(split)-1:
|
||||
extrawhitespace := len(strings.TrimLeft(split[index], " ")) - len(word)
|
||||
word = split[index+1]
|
||||
adjust = len(word) + extrawhitespace
|
||||
default:
|
||||
adjust = len(word) - pos
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (rl *Instance) emacsBackwardWord(tokeniser tokeniser) (adjust int) {
|
||||
split, index, pos := tokeniser(rl.line, rl.pos)
|
||||
if len(split) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
switch {
|
||||
case len(split) == 0:
|
||||
return
|
||||
case pos == 0 && index != 0:
|
||||
adjust = len(split[index-1])
|
||||
default:
|
||||
adjust = pos
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -11,6 +11,13 @@ import (
|
|||
// It also calculates the runes in the string as well as any non-printable escape codes.
|
||||
func (rl *Instance) SetPrompt(s string) {
|
||||
rl.mainPrompt = s
|
||||
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,
|
||||
|
@ -20,7 +27,7 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
|||
// We adjust cursor movement, depending on which mode we're currently in.
|
||||
if !rl.modeTabCompletion {
|
||||
rl.tcUsedY = 1
|
||||
// Account for the hint line
|
||||
// Account for the info line
|
||||
} else if rl.modeTabCompletion && rl.modeAutoFind {
|
||||
rl.tcUsedY = 0
|
||||
} else {
|
||||
|
@ -40,7 +47,7 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
|||
moveCursorUp(1)
|
||||
}
|
||||
rl.stillOnRefresh = true
|
||||
moveCursorUp(rl.hintY + rl.tcUsedY)
|
||||
moveCursorUp(rl.infoY + rl.tcUsedY)
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
print("\r\n" + seqClearScreenBelow)
|
||||
|
||||
|
@ -68,12 +75,11 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
|||
|
||||
// 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 {
|
||||
rl.tcUsedY = 1
|
||||
// Account for the hint line
|
||||
// Account for the info line
|
||||
} else if rl.modeTabCompletion && rl.modeAutoFind {
|
||||
rl.tcUsedY = 0
|
||||
} else {
|
||||
|
@ -82,7 +88,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
|||
|
||||
// Update the prompt if a special has been passed.
|
||||
if prompt != "" {
|
||||
rl.mainPrompt = prompt
|
||||
rl.SetPrompt(prompt)
|
||||
}
|
||||
|
||||
if rl.Multiline {
|
||||
|
@ -91,7 +97,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
|||
|
||||
// Clear the input line and everything below
|
||||
print(seqClearLine)
|
||||
moveCursorUp(rl.hintY + rl.tcUsedY)
|
||||
moveCursorUp(rl.infoY + rl.tcUsedY)
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
print("\r\n" + seqClearScreenBelow)
|
||||
|
||||
|
@ -118,7 +124,7 @@ func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine boo
|
|||
// We adjust cursor movement, depending on which mode we're currently in.
|
||||
if !rl.modeTabCompletion {
|
||||
rl.tcUsedY = 1
|
||||
} else if rl.modeTabCompletion && rl.modeAutoFind { // Account for the hint line
|
||||
} else if rl.modeTabCompletion && rl.modeAutoFind { // Account for the info line
|
||||
rl.tcUsedY = 0
|
||||
} else {
|
||||
rl.tcUsedY = 1
|
||||
|
@ -137,7 +143,7 @@ func (rl *Instance) RefreshPromptCustom(prompt string, offset int, clearLine boo
|
|||
|
||||
// Update the prompt if a special has been passed.
|
||||
if prompt != "" {
|
||||
rl.mainPrompt = prompt
|
||||
rl.SetPrompt(prompt)
|
||||
}
|
||||
|
||||
// Add a new line if needed
|
||||
|
@ -185,6 +191,7 @@ func (rl *Instance) computePrompt() (prompt []rune) {
|
|||
|
||||
// Strip color escapes
|
||||
rl.promptLen = getRealLength(string(rl.realPrompt))
|
||||
rl.rightPromptLen = getRealLength(string(rl.rightPrompt))
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -205,3 +212,11 @@ func getRealLength(s string) (l int) {
|
|||
stripped := ansi.Strip(s)
|
||||
return uniseg.GraphemeClusterCount(stripped)
|
||||
}
|
||||
|
||||
func (rl *Instance) echoRightPrompt() {
|
||||
if rl.fullX < GetTermWidth() - rl.rightPromptLen - 1 {
|
||||
moveCursorForwards(GetTermWidth())
|
||||
moveCursorBackwards(rl.rightPromptLen)
|
||||
print(rl.rightPrompt)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@ package readline
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var rxMultiline = regexp.MustCompile(`[\r\n]+`)
|
||||
|
@ -38,11 +40,12 @@ func (rl *Instance) Readline() (string, error) {
|
|||
rl.modeViMode = VimInsert
|
||||
rl.pos = 0
|
||||
rl.posY = 0
|
||||
rl.tcPrefix = ""
|
||||
|
||||
// Completion && hints init
|
||||
rl.resetHintText()
|
||||
// Completion && infos init
|
||||
rl.resetInfoText()
|
||||
rl.resetTabCompletion()
|
||||
rl.getHintText()
|
||||
rl.getInfoText()
|
||||
|
||||
// History Init
|
||||
// We need this set to the last command, so that we can access it quickly
|
||||
|
@ -62,7 +65,7 @@ func (rl *Instance) Readline() (string, error) {
|
|||
return string(rl.line), nil
|
||||
}
|
||||
|
||||
// Finally, print any hints or completions
|
||||
// Finally, print any info or completions
|
||||
// if the TabCompletion engines so desires
|
||||
rl.renderHelpers()
|
||||
|
||||
|
@ -76,6 +79,12 @@ func (rl *Instance) Readline() (string, error) {
|
|||
var err error
|
||||
i, err = os.Stdin.Read(b)
|
||||
if err != nil {
|
||||
if errors.Is(err, syscall.EAGAIN) {
|
||||
err = syscall.SetNonblock(syscall.Stdin, false)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
@ -127,8 +136,8 @@ func (rl *Instance) Readline() (string, error) {
|
|||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
if len(ret.HintText) > 0 {
|
||||
rl.hintText = ret.HintText
|
||||
if len(ret.InfoText) > 0 {
|
||||
rl.infoText = ret.InfoText
|
||||
rl.clearHelpers()
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
@ -160,9 +169,18 @@ func (rl *Instance) Readline() (string, error) {
|
|||
rl.clearHelpers()
|
||||
return "", CtrlC
|
||||
|
||||
case charEOF:
|
||||
rl.clearHelpers()
|
||||
return "", EOF
|
||||
case charEOF: // ctrl d
|
||||
if len(rl.line) == 0 {
|
||||
rl.clearHelpers()
|
||||
return "", EOF
|
||||
}
|
||||
if rl.modeTabFind {
|
||||
rl.backspaceTabFind()
|
||||
} else {
|
||||
if (rl.pos < len(rl.line)) {
|
||||
rl.deleteBackspace(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear screen
|
||||
case charCtrlL:
|
||||
|
@ -173,8 +191,8 @@ func (rl *Instance) Readline() (string, error) {
|
|||
}
|
||||
print(seqClearScreenBelow)
|
||||
|
||||
rl.resetHintText()
|
||||
rl.getHintText()
|
||||
rl.resetInfoText()
|
||||
rl.getInfoText()
|
||||
rl.renderHelpers()
|
||||
|
||||
// Line Editing ------------------------------------------------------------------------------------
|
||||
|
@ -188,6 +206,16 @@ func (rl *Instance) Readline() (string, error) {
|
|||
rl.resetHelpers()
|
||||
rl.updateHelpers()
|
||||
|
||||
case charCtrlK:
|
||||
if rl.modeTabCompletion {
|
||||
rl.resetVirtualComp(true)
|
||||
}
|
||||
// Delete everything after the cursor position
|
||||
rl.saveBufToRegister(rl.line[rl.pos:])
|
||||
rl.deleteToEnd()
|
||||
rl.resetHelpers()
|
||||
rl.updateHelpers()
|
||||
|
||||
case charBackspace, charBackspace2:
|
||||
// When currently in history completion, we refresh and automatically
|
||||
// insert the first (filtered) candidate, virtually
|
||||
|
@ -213,7 +241,7 @@ func (rl *Instance) Readline() (string, error) {
|
|||
// Vim mode has different behaviors
|
||||
if rl.InputMode == Vim {
|
||||
if rl.modeViMode == VimInsert {
|
||||
rl.backspace()
|
||||
rl.backspace(false)
|
||||
} else if rl.pos != 0 {
|
||||
rl.pos--
|
||||
}
|
||||
|
@ -222,7 +250,7 @@ func (rl *Instance) Readline() (string, error) {
|
|||
}
|
||||
|
||||
// Else emacs deletes a character
|
||||
rl.backspace()
|
||||
rl.backspace(false)
|
||||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
|
@ -387,6 +415,10 @@ func (rl *Instance) Readline() (string, error) {
|
|||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
case charCtrlUnderscore:
|
||||
rl.undoLast()
|
||||
rl.viUndoSkipAppend = true
|
||||
|
||||
case '\r':
|
||||
fallthrough
|
||||
case '\n':
|
||||
|
@ -516,22 +548,27 @@ func (rl *Instance) editorInput(r []rune) {
|
|||
|
||||
case VimReplaceMany:
|
||||
for _, char := range r {
|
||||
rl.deleteX()
|
||||
if rl.pos != len(rl.line) {
|
||||
rl.deleteX()
|
||||
}
|
||||
rl.insert([]rune{char})
|
||||
}
|
||||
rl.refreshVimStatus()
|
||||
|
||||
default:
|
||||
// For some reason Ctrl+k messes with the input line, so ignore it.
|
||||
if r[0] == 11 {
|
||||
// Don't insert control keys
|
||||
if r[0] >= 1 && r[0] <= 31 {
|
||||
return
|
||||
}
|
||||
// We reset the history nav counter each time we come here:
|
||||
// We don't need it when inserting text.
|
||||
rl.histNavIdx = 0
|
||||
rl.insert(r)
|
||||
rl.writeHintText()
|
||||
}
|
||||
|
||||
rl.echoRightPrompt()
|
||||
|
||||
if len(rl.multisplit) == 0 {
|
||||
rl.syntaxCompletion()
|
||||
}
|
||||
|
@ -625,6 +662,8 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||
}
|
||||
rl.mainHist = true
|
||||
rl.walkHistory(1)
|
||||
moveCursorForwards(len(rl.line) - rl.pos)
|
||||
rl.pos = len(rl.line)
|
||||
|
||||
case seqDown:
|
||||
if rl.modeTabCompletion {
|
||||
|
@ -636,6 +675,8 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||
}
|
||||
rl.mainHist = true
|
||||
rl.walkHistory(-1)
|
||||
moveCursorForwards(len(rl.line) - rl.pos)
|
||||
rl.pos = len(rl.line)
|
||||
|
||||
case seqForwards:
|
||||
if rl.modeTabCompletion {
|
||||
|
@ -647,8 +688,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||
}
|
||||
if (rl.modeViMode == VimInsert && rl.pos < len(rl.line)) ||
|
||||
(rl.modeViMode != VimInsert && rl.pos < len(rl.line)-1) {
|
||||
moveCursorForwards(1)
|
||||
rl.pos++
|
||||
rl.moveCursorByAdjust(1)
|
||||
}
|
||||
rl.updateHelpers()
|
||||
rl.viUndoSkipAppend = true
|
||||
|
@ -663,10 +703,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||
rl.renderHelpers()
|
||||
return
|
||||
}
|
||||
if rl.pos > 0 {
|
||||
moveCursorBackwards(1)
|
||||
rl.pos--
|
||||
}
|
||||
rl.moveCursorByAdjust(-1)
|
||||
rl.viUndoSkipAppend = true
|
||||
rl.updateHelpers()
|
||||
|
||||
|
@ -689,32 +726,64 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||
rl.updateHelpers()
|
||||
return
|
||||
case seqCtrlRightArrow:
|
||||
rl.insert(rl.hintText)
|
||||
rl.moveCursorByAdjust(rl.viJumpW(tokeniseLine))
|
||||
rl.updateHelpers()
|
||||
return
|
||||
|
||||
case seqDelete:
|
||||
case seqDelete,seqDelete2:
|
||||
if rl.modeTabFind {
|
||||
rl.backspaceTabFind()
|
||||
} else {
|
||||
rl.deleteBackspace()
|
||||
if (rl.pos < len(rl.line)) {
|
||||
rl.deleteBackspace(true)
|
||||
}
|
||||
}
|
||||
|
||||
case seqHome, seqHomeSc:
|
||||
if rl.modeTabCompletion {
|
||||
return
|
||||
}
|
||||
moveCursorBackwards(rl.pos)
|
||||
rl.pos = 0
|
||||
rl.moveCursorByAdjust(-rl.pos)
|
||||
rl.updateHelpers()
|
||||
rl.viUndoSkipAppend = true
|
||||
|
||||
case seqEnd, seqEndSc:
|
||||
if rl.modeTabCompletion {
|
||||
return
|
||||
}
|
||||
moveCursorForwards(len(rl.line) - rl.pos)
|
||||
rl.pos = len(rl.line)
|
||||
rl.moveCursorByAdjust(len(rl.line) - rl.pos)
|
||||
rl.updateHelpers()
|
||||
rl.viUndoSkipAppend = true
|
||||
|
||||
case seqAltB:
|
||||
if rl.modeTabCompletion {
|
||||
return
|
||||
}
|
||||
|
||||
// This is only available in Insert mode
|
||||
if rl.modeViMode != VimInsert {
|
||||
return
|
||||
}
|
||||
|
||||
move := rl.emacsBackwardWord(tokeniseLine)
|
||||
rl.moveCursorByAdjust(-move)
|
||||
rl.updateHelpers()
|
||||
|
||||
case seqAltF:
|
||||
if rl.modeTabCompletion {
|
||||
return
|
||||
}
|
||||
|
||||
// This is only available in Insert mode
|
||||
if rl.modeViMode != VimInsert {
|
||||
return
|
||||
}
|
||||
|
||||
move := rl.emacsForwardWord(tokeniseLine)
|
||||
rl.moveCursorByAdjust(move)
|
||||
rl.updateHelpers()
|
||||
|
||||
case seqAltR:
|
||||
rl.resetVirtualComp(false)
|
||||
// For some modes only, if we are in vim Keys mode,
|
||||
|
@ -733,6 +802,36 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||
rl.updateTabFind([]rune{})
|
||||
rl.viUndoSkipAppend = true
|
||||
|
||||
case seqAltBackspace:
|
||||
if rl.modeTabCompletion {
|
||||
rl.resetVirtualComp(false)
|
||||
}
|
||||
// This is only available in Insert mode
|
||||
if rl.modeViMode != VimInsert {
|
||||
return
|
||||
}
|
||||
|
||||
rl.saveToRegister(rl.viJumpB(tokeniseLine))
|
||||
rl.viDeleteByAdjust(rl.viJumpB(tokeniseLine))
|
||||
rl.updateHelpers()
|
||||
|
||||
case seqCtrlDelete, seqCtrlDelete2, seqAltD:
|
||||
if rl.modeTabCompletion {
|
||||
rl.resetVirtualComp(false)
|
||||
}
|
||||
rl.saveToRegister(rl.emacsForwardWord(tokeniseLine))
|
||||
// vi delete, emacs forward, funny huh
|
||||
rl.viDeleteByAdjust(rl.emacsForwardWord(tokeniseLine))
|
||||
rl.updateHelpers()
|
||||
|
||||
case seqAltDelete:
|
||||
if rl.modeTabCompletion {
|
||||
rl.resetVirtualComp(false)
|
||||
}
|
||||
rl.saveToRegister(-rl.emacsBackwardWord(tokeniseLine))
|
||||
rl.viDeleteByAdjust(-rl.emacsBackwardWord(tokeniseLine))
|
||||
rl.updateHelpers()
|
||||
|
||||
default:
|
||||
if rl.modeTabFind {
|
||||
return
|
||||
|
@ -768,6 +867,8 @@ func (rl *Instance) escapeSeq(r []rune) {
|
|||
}
|
||||
|
||||
func (rl *Instance) carridgeReturn() {
|
||||
rl.moveCursorByAdjust(len(rl.line))
|
||||
rl.updateHelpers()
|
||||
rl.clearHelpers()
|
||||
print("\r\n")
|
||||
if rl.HistoryAutoWrite {
|
||||
|
|
|
@ -259,9 +259,9 @@ func (r *registers) resetRegister() {
|
|||
// The user can show registers completions and insert, no matter the cursor position.
|
||||
func (rl *Instance) completeRegisters() (groups []*CompletionGroup) {
|
||||
|
||||
// We set the hint exceptionally
|
||||
hint := BLUE + "-- registers --" + RESET
|
||||
rl.hintText = []rune(hint)
|
||||
// We set the info exceptionally
|
||||
info := BLUE + "-- registers --" + RESET
|
||||
rl.infoText = []rune(info)
|
||||
|
||||
// Make the groups
|
||||
anonRegs := &CompletionGroup{
|
||||
|
|
|
@ -73,7 +73,12 @@ func (rl *Instance) insertCandidate() {
|
|||
|
||||
// Ensure no indexing error happens with prefix
|
||||
if len(completion) >= prefix {
|
||||
rl.insert([]rune(completion[prefix:]))
|
||||
comp := completion[prefix:]
|
||||
if completion[:prefix] != rl.tcPrefix {
|
||||
rl.viDeleteByAdjust(-prefix)
|
||||
comp = completion
|
||||
}
|
||||
rl.insert([]rune(comp))
|
||||
if !cur.TrimSlash && !cur.NoSpace {
|
||||
rl.insert([]rune(" "))
|
||||
}
|
||||
|
|
|
@ -93,19 +93,16 @@ func (rl *Instance) getTabSearchCompletion() {
|
|||
}
|
||||
rl.getCurrentGroup()
|
||||
|
||||
// Set the hint for this completion mode
|
||||
rl.hintText = append([]rune("Completion search: "), rl.tfLine...)
|
||||
|
||||
// Set the hint for this completion mode
|
||||
rl.hintText = append([]rune("Completion search: "), rl.tfLine...)
|
||||
// Set the info for this completion mode
|
||||
rl.infoText = append([]rune("Completion search: "), rl.tfLine...)
|
||||
|
||||
for _, g := range rl.tcGroups {
|
||||
g.updateTabFind(rl)
|
||||
}
|
||||
|
||||
// If total number of matches is zero, we directly change the hint, and return
|
||||
// If total number of matches is zero, we directly change the info, and return
|
||||
if comps, _, _ := rl.getCompletionCount(); comps == 0 {
|
||||
rl.hintText = append(rl.hintText, []rune(DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
|
||||
rl.infoText = append(rl.infoText, []rune(DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,25 +117,25 @@ func (rl *Instance) getHistorySearchCompletion() {
|
|||
rl.tcGroups = checkNilItems(rl.tcGroups) // Avoid nil maps in groups
|
||||
rl.getCurrentGroup() // Make sure there is a current group
|
||||
|
||||
// The history hint is already set, but overwrite it if we don't have completions
|
||||
// The history info is already set, but overwrite it if we don't have completions
|
||||
if len(rl.tcGroups[0].Suggestions) == 0 {
|
||||
rl.histHint = []rune(fmt.Sprintf("%s%s%s %s", DIM, RED,
|
||||
rl.histInfo = []rune(fmt.Sprintf("%s%s%s %s", DIM, RED,
|
||||
"No command history source, or empty (Ctrl-G/Esc to cancel)", RESET))
|
||||
rl.hintText = rl.histHint
|
||||
rl.infoText = rl.histInfo
|
||||
return
|
||||
}
|
||||
|
||||
// Set the hint line with everything
|
||||
rl.histHint = append([]rune("\033[38;5;183m"+string(rl.histHint)+RESET), rl.tfLine...)
|
||||
rl.histHint = append(rl.histHint, []rune(RESET)...)
|
||||
rl.hintText = rl.histHint
|
||||
// Set the info line with everything
|
||||
rl.histInfo = append([]rune("\033[38;5;183m"+string(rl.histInfo)+RESET), rl.tfLine...)
|
||||
rl.histInfo = append(rl.histInfo, []rune(RESET)...)
|
||||
rl.infoText = rl.histInfo
|
||||
|
||||
// Refresh filtered candidates
|
||||
rl.tcGroups[0].updateTabFind(rl)
|
||||
|
||||
// If no items matched history, add hint text that we failed to search
|
||||
// If no items matched history, add info text that we failed to search
|
||||
if len(rl.tcGroups[0].Suggestions) == 0 {
|
||||
rl.hintText = append(rl.histHint, []rune(DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
|
||||
rl.infoText = append(rl.histInfo, []rune(DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -301,15 +298,15 @@ func (rl *Instance) cropCompletions(comps string) (cropped string, usedY int) {
|
|||
// Else we go on, but we have more comps than what allowed:
|
||||
// we will add a line to the end of the comps, giving the actualized
|
||||
// number of completions remaining and not printed
|
||||
var moreComps = func(cropped string, offset int) (hinted string, noHint bool) {
|
||||
var moreComps = func(cropped string, offset int) (infoed string, noInfo bool) {
|
||||
_, _, adjusted := rl.getCompletionCount()
|
||||
remain := adjusted - offset
|
||||
if remain == 0 {
|
||||
return cropped, true
|
||||
}
|
||||
hint := fmt.Sprintf(DIM+YELLOW+" %d more completions... (scroll down to show)"+RESET+"\n", remain)
|
||||
hinted = cropped + hint
|
||||
return hinted, false
|
||||
info := fmt.Sprintf(DIM+YELLOW+" %d more completions... (scroll down to show)"+RESET+"\n", remain)
|
||||
infoed = cropped + info
|
||||
return infoed, false
|
||||
}
|
||||
|
||||
// Get the current absolute candidate position (prev groups x suggestions + curGroup.tcPosY)
|
||||
|
@ -512,7 +509,7 @@ func (rl *Instance) hasOneCandidate() bool {
|
|||
// - The terminal lengh
|
||||
// we use this function to prompt for confirmation before printing comps.
|
||||
func (rl *Instance) promptCompletionConfirm(sentence string) {
|
||||
rl.hintText = []rune(sentence)
|
||||
rl.infoText = []rune(sentence)
|
||||
|
||||
rl.compConfirmWait = true
|
||||
rl.viUndoSkipAppend = true
|
||||
|
|
|
@ -33,7 +33,7 @@ func (rl *Instance) updateTabFind(r []rune) {
|
|||
var err error
|
||||
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
|
||||
if err != nil {
|
||||
rl.hintText = []rune(Red("Failed to match search regexp"))
|
||||
rl.infoText = []rune(Red("Failed to match search regexp"))
|
||||
}
|
||||
|
||||
// We update and print
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package readline
|
||||
|
||||
import "golang.org/x/text/width"
|
||||
|
||||
// updateHelpers is a key part of the whole refresh process:
|
||||
// it should coordinate reprinting the input line, any hints and completions
|
||||
// it should coordinate reprinting the input line, any Infos and completions
|
||||
// and manage to get back to the current (computed) cursor coordinates
|
||||
func (rl *Instance) updateHelpers() {
|
||||
|
||||
// Load all hints & completions before anything.
|
||||
// Thus overwrites anything having been dirtily added/forced/modified, like rl.SetHintText()
|
||||
// Load all Infos & completions before anything.
|
||||
// Thus overwrites anything having been dirtily added/forced/modified, like rl.SetInfoText()
|
||||
rl.getInfoText()
|
||||
rl.getHintText()
|
||||
if rl.modeTabCompletion {
|
||||
rl.getTabCompletion()
|
||||
|
@ -20,6 +23,23 @@ func (rl *Instance) updateHelpers() {
|
|||
rl.renderHelpers()
|
||||
}
|
||||
|
||||
const tabWidth = 4
|
||||
|
||||
func getWidth(x []rune) int {
|
||||
var w int
|
||||
for _, j := range x {
|
||||
k := width.LookupRune(j).Kind()
|
||||
if j == '\t' {
|
||||
w += tabWidth
|
||||
} else if k == width.EastAsianWide || k == width.EastAsianFullwidth {
|
||||
w += 2
|
||||
} else {
|
||||
w++
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Update reference should be called only once in a "loop" (not Readline(), but key control loop)
|
||||
func (rl *Instance) updateReferences() {
|
||||
|
||||
|
@ -32,11 +52,11 @@ func (rl *Instance) updateReferences() {
|
|||
|
||||
var fullLine, cPosLine int
|
||||
if len(rl.currentComp) > 0 {
|
||||
fullLine = len(rl.lineComp)
|
||||
cPosLine = len(rl.lineComp[:rl.pos])
|
||||
fullLine = getWidth(rl.lineComp)
|
||||
cPosLine = getWidth(rl.lineComp[:rl.pos])
|
||||
} else {
|
||||
fullLine = len(rl.line)
|
||||
cPosLine = len(rl.line[:rl.pos])
|
||||
fullLine = getWidth(rl.line)
|
||||
cPosLine = getWidth(rl.line[:rl.pos])
|
||||
}
|
||||
|
||||
// We need the X offset of the whole line
|
||||
|
@ -46,6 +66,10 @@ func (rl *Instance) updateReferences() {
|
|||
fullRest := toEndLine % GetTermWidth()
|
||||
rl.fullX = fullRest
|
||||
|
||||
if fullRest == 0 && fullOffset > 0 {
|
||||
print("\n")
|
||||
}
|
||||
|
||||
// Use rl.pos value to get the offset to go TO/FROM the CURRENT POSITION
|
||||
lineToCursorPos := rl.promptLen + cPosLine
|
||||
offsetToCursor := lineToCursorPos / GetTermWidth()
|
||||
|
@ -75,11 +99,11 @@ func (rl *Instance) resetHelpers() {
|
|||
rl.modeAutoFind = false
|
||||
|
||||
// Now reset all below-input helpers
|
||||
rl.resetHintText()
|
||||
rl.resetInfoText()
|
||||
rl.resetTabCompletion()
|
||||
}
|
||||
|
||||
// clearHelpers - Clears everything: prompt, input, hints & comps,
|
||||
// clearHelpers - Clears everything: prompt, input, Infos & comps,
|
||||
// and comes back at the prompt.
|
||||
func (rl *Instance) clearHelpers() {
|
||||
|
||||
|
@ -97,25 +121,42 @@ func (rl *Instance) clearHelpers() {
|
|||
moveCursorForwards(rl.posX)
|
||||
}
|
||||
|
||||
// renderHelpers - pritns all components (prompt, line, hints & comps)
|
||||
// renderHelpers - pritns all components (prompt, line, Infos & comps)
|
||||
// and replaces the cursor to its current position. This function never
|
||||
// computes or refreshes any value, except from inside the echo function.
|
||||
func (rl *Instance) renderHelpers() {
|
||||
|
||||
// Optional, because neutral on placement
|
||||
// when the instance is in this state we want it to be "below" the user's
|
||||
// input for it to be aligned properly
|
||||
if !rl.compConfirmWait {
|
||||
rl.writeHintText()
|
||||
}
|
||||
rl.echo()
|
||||
if rl.modeTabCompletion {
|
||||
// in tab complete mode we want it to update
|
||||
// when something has been selected
|
||||
// (dynamic!!)
|
||||
rl.getHintText()
|
||||
rl.writeHintText()
|
||||
} else if !rl.compConfirmWait {
|
||||
// for the same reason above of wanting it below user input, do nothing here
|
||||
} else {
|
||||
rl.writeHintText()
|
||||
}
|
||||
|
||||
rl.echoRightPrompt()
|
||||
|
||||
// Go at beginning of first line after input remainder
|
||||
moveCursorDown(rl.fullY - rl.posY)
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
|
||||
// Print hints, check for any confirmation hint current.
|
||||
// (do not overwrite the confirmation question hint)
|
||||
// Print Infos, check for any confirmation Info current.
|
||||
// (do not overwrite the confirmation question Info)
|
||||
if !rl.compConfirmWait {
|
||||
if len(rl.hintText) > 0 {
|
||||
if len(rl.infoText) > 0 {
|
||||
print("\n")
|
||||
}
|
||||
rl.writeHintText()
|
||||
rl.writeInfoText()
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
|
||||
// Print completions and go back to beginning of this line
|
||||
|
@ -126,17 +167,17 @@ func (rl *Instance) renderHelpers() {
|
|||
}
|
||||
|
||||
// If we are still waiting for the user to confirm too long completions
|
||||
// Immediately refresh the hints
|
||||
// Immediately refresh the Infos
|
||||
if rl.compConfirmWait {
|
||||
print("\n")
|
||||
rl.writeHintText()
|
||||
rl.getHintText()
|
||||
rl.writeInfoText()
|
||||
rl.getInfoText()
|
||||
moveCursorBackwards(GetTermWidth())
|
||||
}
|
||||
|
||||
// Anyway, compensate for hint printout
|
||||
if len(rl.hintText) > 0 {
|
||||
moveCursorUp(rl.hintY)
|
||||
// Anyway, compensate for Info printout
|
||||
if len(rl.infoText) > 0 {
|
||||
moveCursorUp(rl.infoY)
|
||||
} else if !rl.compConfirmWait {
|
||||
moveCursorUp(1)
|
||||
} else if rl.compConfirmWait {
|
||||
|
|
|
@ -399,22 +399,22 @@ func (rl *Instance) refreshVimStatus() {
|
|||
rl.updateHelpers()
|
||||
}
|
||||
|
||||
// viHintMessage - lmorg's way of showing Vim status is to overwrite the hint.
|
||||
// viInfoMessage - lmorg's way of showing Vim status is to overwrite the info.
|
||||
// Currently not used, as there is a possibility to show the current Vim mode in the prompt.
|
||||
func (rl *Instance) viHintMessage() {
|
||||
func (rl *Instance) viInfoMessage() {
|
||||
switch rl.modeViMode {
|
||||
case VimKeys:
|
||||
rl.hintText = []rune("-- VIM KEYS -- (press `i` to return to normal editing mode)")
|
||||
rl.infoText = []rune("-- VIM KEYS -- (press `i` to return to normal editing mode)")
|
||||
case VimInsert:
|
||||
rl.hintText = []rune("-- INSERT --")
|
||||
rl.infoText = []rune("-- INSERT --")
|
||||
case VimReplaceOnce:
|
||||
rl.hintText = []rune("-- REPLACE CHARACTER --")
|
||||
rl.infoText = []rune("-- REPLACE CHARACTER --")
|
||||
case VimReplaceMany:
|
||||
rl.hintText = []rune("-- REPLACE --")
|
||||
rl.infoText = []rune("-- REPLACE --")
|
||||
case VimDelete:
|
||||
rl.hintText = []rune("-- DELETE --")
|
||||
rl.infoText = []rune("-- DELETE --")
|
||||
default:
|
||||
rl.getHintText()
|
||||
rl.getInfoText()
|
||||
}
|
||||
|
||||
rl.clearHelpers()
|
||||
|
|
|
@ -33,7 +33,7 @@ func (rl *Instance) viDelete(r rune) {
|
|||
rl.saveBufToRegister(rl.line)
|
||||
rl.clearLine()
|
||||
rl.resetHelpers()
|
||||
rl.getHintText()
|
||||
rl.getInfoText()
|
||||
|
||||
case 'e':
|
||||
vii := rl.getViIterations()
|
||||
|
|
264
rl.go
264
rl.go
|
@ -5,23 +5,26 @@ import (
|
|||
"io"
|
||||
"strings"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
"github.com/maxlandon/readline"
|
||||
"github.com/yuin/gopher-lua"
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
type lineReader struct {
|
||||
rl *readline.Instance
|
||||
}
|
||||
var fileHist *fileHistory
|
||||
var hinter *rt.Closure
|
||||
var highlighter *rt.Closure
|
||||
|
||||
// other gophers might hate this naming but this is local, shut up
|
||||
func newLineReader(prompt string, noHist bool) *lineReader {
|
||||
rl := readline.NewInstance()
|
||||
// we don't mind hilbish.read rl instances having completion,
|
||||
// but it cant have shared history
|
||||
if !noHist {
|
||||
fileHist = newFileHistory()
|
||||
rl.SetHistoryCtrlR("file", fileHist)
|
||||
rl.SetHistoryCtrlR("History", fileHist)
|
||||
rl.HistoryAutoWrite = false
|
||||
}
|
||||
rl.ShowVimMode = false
|
||||
|
@ -44,9 +47,45 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||
}
|
||||
hooks.Em.Emit("hilbish.vimAction", actionStr, args)
|
||||
}
|
||||
rl.HintText = func(line []rune, pos int) []rune {
|
||||
if hinter == nil {
|
||||
return []rune{}
|
||||
}
|
||||
|
||||
retVal, err := rt.Call1(l.MainThread(), rt.FunctionValue(highlighter),
|
||||
rt.StringValue(string(line)), rt.IntValue(int64(pos)))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return []rune{}
|
||||
}
|
||||
|
||||
hintText := ""
|
||||
if luaStr, ok := retVal.TryString(); ok {
|
||||
hintText = luaStr
|
||||
}
|
||||
|
||||
return []rune(hintText)
|
||||
}
|
||||
rl.SyntaxHighlighter = func(line []rune) string {
|
||||
if highlighter == nil {
|
||||
return string(line)
|
||||
}
|
||||
retVal, err := rt.Call1(l.MainThread(), rt.FunctionValue(highlighter),
|
||||
rt.StringValue(string(line)))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return string(line)
|
||||
}
|
||||
|
||||
highlighted := ""
|
||||
if luaStr, ok := retVal.TryString(); ok {
|
||||
highlighted = luaStr
|
||||
}
|
||||
|
||||
return highlighted
|
||||
}
|
||||
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
|
||||
ctx := string(line)
|
||||
var completions []string
|
||||
|
||||
var compGroup []*readline.CompletionGroup
|
||||
|
||||
|
@ -75,23 +114,20 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||
return prefix, compGroup
|
||||
} else {
|
||||
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
|
||||
luaFields := l.NewTable()
|
||||
for _, f := range fields {
|
||||
luaFields.Append(lua.LString(f))
|
||||
luaFields := rt.NewTable()
|
||||
for i, f := range fields {
|
||||
luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
|
||||
}
|
||||
err := l.CallByParam(lua.P{
|
||||
Fn: completecb,
|
||||
NRet: 1,
|
||||
Protect: true,
|
||||
}, lua.LString(query), lua.LString(ctx), luaFields)
|
||||
|
||||
// we must keep the holy 80 cols
|
||||
luacompleteTable, err := rt.Call1(l.MainThread(),
|
||||
rt.FunctionValue(completecb), rt.StringValue(query),
|
||||
rt.StringValue(ctx), rt.TableValue(luaFields))
|
||||
|
||||
if err != nil {
|
||||
return "", compGroup
|
||||
}
|
||||
|
||||
luacompleteTable := l.Get(-1)
|
||||
l.Pop(1)
|
||||
|
||||
/*
|
||||
as an example with git,
|
||||
completion table should be structured like:
|
||||
|
@ -116,60 +152,98 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
|||
it is the responsibility of the completer
|
||||
to work on subcommands and subcompletions
|
||||
*/
|
||||
if cmpTbl, ok := luacompleteTable.(*lua.LTable); ok {
|
||||
cmpTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||
if key.Type() == lua.LTNumber {
|
||||
// completion group
|
||||
if value.Type() == lua.LTTable {
|
||||
luaCmpGroup := value.(*lua.LTable)
|
||||
compType := luaCmpGroup.RawGet(lua.LString("type"))
|
||||
compItems := luaCmpGroup.RawGet(lua.LString("items"))
|
||||
if compType.Type() != lua.LTString {
|
||||
l.RaiseError("bad type name for completion (expected string, got %v)", compType.Type().String())
|
||||
}
|
||||
if compItems.Type() != lua.LTTable {
|
||||
l.RaiseError("bad items for completion (expected table, got %v)", compItems.Type().String())
|
||||
}
|
||||
var items []string
|
||||
itemDescriptions := make(map[string]string)
|
||||
compItems.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) {
|
||||
if k.Type() == lua.LTString {
|
||||
// ['--flag'] = {'description', '--flag-alias'}
|
||||
itm := v.(*lua.LTable)
|
||||
items = append(items, k.String())
|
||||
itemDescriptions[k.String()] = itm.RawGet(lua.LNumber(1)).String()
|
||||
} else {
|
||||
items = append(items, v.String())
|
||||
}
|
||||
})
|
||||
if cmpTbl, ok := luacompleteTable.TryTable(); ok {
|
||||
nextVal := rt.NilValue
|
||||
for {
|
||||
next, val, ok := cmpTbl.Next(nextVal)
|
||||
if next == rt.NilValue {
|
||||
break
|
||||
}
|
||||
nextVal = next
|
||||
|
||||
var dispType readline.TabDisplayType
|
||||
switch compType.String() {
|
||||
case "grid": dispType = readline.TabDisplayGrid
|
||||
case "list": dispType = readline.TabDisplayList
|
||||
// need special cases, will implement later
|
||||
//case "map": dispType = readline.TabDisplayMap
|
||||
_, ok = next.TryInt()
|
||||
valTbl, okk := val.TryTable()
|
||||
if !ok || !okk {
|
||||
// TODO: error?
|
||||
break
|
||||
}
|
||||
|
||||
luaCompType := valTbl.Get(rt.StringValue("type"))
|
||||
luaCompItems := valTbl.Get(rt.StringValue("items"))
|
||||
|
||||
compType, ok := luaCompType.TryString()
|
||||
compItems, okk := luaCompItems.TryTable()
|
||||
if !ok || !okk {
|
||||
// TODO: error
|
||||
break
|
||||
}
|
||||
|
||||
var items []string
|
||||
itemDescriptions := make(map[string]string)
|
||||
nxVal := rt.NilValue
|
||||
for {
|
||||
nx, vl, _ := compItems.Next(nxVal)
|
||||
if nx == rt.NilValue {
|
||||
break
|
||||
}
|
||||
nxVal = nx
|
||||
|
||||
if tstr := nx.Type(); tstr == rt.StringType {
|
||||
// ['--flag'] = {'description', '--flag-alias'}
|
||||
nxStr, ok := nx.TryString()
|
||||
vlTbl, okk := vl.TryTable()
|
||||
if !ok || !okk {
|
||||
// TODO: error
|
||||
continue
|
||||
}
|
||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
||||
DisplayType: dispType,
|
||||
Descriptions: itemDescriptions,
|
||||
Suggestions: items,
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
})
|
||||
items = append(items, nxStr)
|
||||
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
||||
if !ok {
|
||||
// TODO: error
|
||||
continue
|
||||
}
|
||||
itemDescriptions[nxStr] = itemDescription
|
||||
} else if tstr == rt.IntType {
|
||||
vlStr, okk := vl.TryString()
|
||||
if !okk {
|
||||
// TODO: error
|
||||
continue
|
||||
}
|
||||
items = append(items, vlStr)
|
||||
} else {
|
||||
// TODO: error
|
||||
continue
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var dispType readline.TabDisplayType
|
||||
switch compType {
|
||||
case "grid": dispType = readline.TabDisplayGrid
|
||||
case "list": dispType = readline.TabDisplayList
|
||||
// need special cases, will implement later
|
||||
//case "map": dispType = readline.TabDisplayMap
|
||||
}
|
||||
|
||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
||||
DisplayType: dispType,
|
||||
Descriptions: itemDescriptions,
|
||||
Suggestions: items,
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(compGroup) == 0 {
|
||||
completions = fileComplete(query, ctx, fields)
|
||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
||||
completions, p := fileComplete(query, ctx, fields)
|
||||
fcompGroup := []*readline.CompletionGroup{{
|
||||
TrimSlash: false,
|
||||
NoSpace: true,
|
||||
Suggestions: completions,
|
||||
})
|
||||
}}
|
||||
|
||||
return p, fcompGroup
|
||||
}
|
||||
}
|
||||
return "", compGroup
|
||||
|
@ -208,6 +282,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) {
|
||||
fileHist.Write(cmd)
|
||||
}
|
||||
|
@ -221,56 +302,65 @@ func (lr *lineReader) Resize() {
|
|||
}
|
||||
|
||||
// lua module
|
||||
func (lr *lineReader) Loader(L *lua.LState) *lua.LTable {
|
||||
lrLua := map[string]lua.LGFunction{
|
||||
"add": lr.luaAddHistory,
|
||||
"all": lr.luaAllHistory,
|
||||
"clear": lr.luaClearHistory,
|
||||
"get": lr.luaGetHistory,
|
||||
"size": lr.luaSize,
|
||||
func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table {
|
||||
lrLua := map[string]util.LuaExport{
|
||||
"add": {lr.luaAddHistory, 1, false},
|
||||
"all": {lr.luaAllHistory, 0, false},
|
||||
"clear": {lr.luaClearHistory, 0, false},
|
||||
"get": {lr.luaGetHistory, 1, false},
|
||||
"size": {lr.luaSize, 0, false},
|
||||
}
|
||||
|
||||
mod := l.SetFuncs(l.NewTable(), lrLua)
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, lrLua)
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func (lr *lineReader) luaAddHistory(l *lua.LState) int {
|
||||
cmd := l.CheckString(1)
|
||||
func (lr *lineReader) luaAddHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lr.AddHistory(cmd)
|
||||
|
||||
return 0
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func (lr *lineReader) luaSize(L *lua.LState) int {
|
||||
L.Push(lua.LNumber(fileHist.Len()))
|
||||
|
||||
return 1
|
||||
func (lr *lineReader) luaSize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
return c.PushingNext1(t.Runtime, rt.IntValue(int64(fileHist.Len()))), nil
|
||||
}
|
||||
|
||||
func (lr *lineReader) luaGetHistory(L *lua.LState) int {
|
||||
idx := L.CheckInt(1)
|
||||
cmd, _ := fileHist.GetLine(idx)
|
||||
L.Push(lua.LString(cmd))
|
||||
func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idx, err := c.IntArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return 0
|
||||
cmd, _ := fileHist.GetLine(int(idx))
|
||||
|
||||
return c.PushingNext1(t.Runtime, rt.StringValue(cmd)), nil
|
||||
}
|
||||
|
||||
func (lr *lineReader) luaAllHistory(L *lua.LState) int {
|
||||
tbl := L.NewTable()
|
||||
func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
tbl := rt.NewTable()
|
||||
size := fileHist.Len()
|
||||
|
||||
for i := 1; i < size; i++ {
|
||||
cmd, _ := fileHist.GetLine(i)
|
||||
tbl.Append(lua.LString(cmd))
|
||||
tbl.Set(rt.IntValue(int64(i)), rt.StringValue(cmd))
|
||||
}
|
||||
|
||||
L.Push(tbl)
|
||||
|
||||
return 0
|
||||
return c.PushingNext1(t.Runtime, rt.TableValue(tbl)), nil
|
||||
}
|
||||
|
||||
func (lr *lineReader) luaClearHistory(l *lua.LState) int {
|
||||
return 0
|
||||
|
||||
func (lr *lineReader) luaClearHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
fileHist.clear()
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
func runnerModeLoader(rtm *rt.Runtime) *rt.Table {
|
||||
exports := map[string]util.LuaExport{
|
||||
"sh": {shRunner, 1, false},
|
||||
"lua": {luaRunner, 1, false},
|
||||
"setMode": {hlrunnerMode, 1, false},
|
||||
}
|
||||
|
||||
mod := rt.NewTable()
|
||||
util.SetExports(rtm, mod, exports)
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func shRunner(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
input, exitCode, err := handleSh(cmd)
|
||||
var luaErr rt.Value = rt.NilValue
|
||||
if err != nil {
|
||||
luaErr = rt.StringValue(err.Error())
|
||||
}
|
||||
|
||||
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) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
input, exitCode, err := handleLua(cmd)
|
||||
var luaErr rt.Value = rt.NilValue
|
||||
if err != nil {
|
||||
luaErr = rt.StringValue(err.Error())
|
||||
}
|
||||
|
||||
return c.PushingNext(t.Runtime, rt.StringValue(input), rt.IntValue(int64(exitCode)), luaErr), nil
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
type timerType int64
|
||||
const (
|
||||
timerInterval timerType = iota
|
||||
timerTimeout
|
||||
)
|
||||
|
||||
type timer struct{
|
||||
id int
|
||||
typ timerType
|
||||
running bool
|
||||
dur time.Duration
|
||||
fun *rt.Closure
|
||||
th *timerHandler
|
||||
ticker *time.Ticker
|
||||
channel chan bool
|
||||
}
|
||||
|
||||
func (t *timer) start() error {
|
||||
if t.running {
|
||||
return errors.New("timer is already running")
|
||||
}
|
||||
|
||||
t.running = true
|
||||
t.th.running++
|
||||
t.ticker = time.NewTicker(t.dur)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-t.ticker.C:
|
||||
_, err := rt.Call1(l.MainThread(), rt.FunctionValue(t.fun))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error in function:\n", err)
|
||||
t.stop()
|
||||
}
|
||||
// only run one for timeout
|
||||
if t.typ == timerTimeout {
|
||||
t.stop()
|
||||
}
|
||||
case <-t.channel:
|
||||
t.ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *timer) stop() error {
|
||||
if !t.running {
|
||||
return errors.New("timer not running")
|
||||
}
|
||||
|
||||
t.channel <- true
|
||||
t.running = false
|
||||
t.th.running--
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *timer) luaStart(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
err := t.start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func (t *timer) luaStop(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"hilbish/util"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
var timers *timerHandler
|
||||
type timerHandler struct {
|
||||
mu *sync.RWMutex
|
||||
timers map[int]*timer
|
||||
latestID int
|
||||
running int
|
||||
}
|
||||
|
||||
func newTimerHandler() *timerHandler {
|
||||
return &timerHandler{
|
||||
timers: make(map[int]*timer),
|
||||
latestID: 0,
|
||||
mu: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (th *timerHandler) create(typ timerType, dur time.Duration, fun *rt.Closure) *timer {
|
||||
th.mu.Lock()
|
||||
defer th.mu.Unlock()
|
||||
|
||||
th.latestID++
|
||||
t := &timer{
|
||||
typ: typ,
|
||||
fun: fun,
|
||||
dur: dur,
|
||||
channel: make(chan bool, 1),
|
||||
th: th,
|
||||
id: th.latestID,
|
||||
}
|
||||
th.timers[th.latestID] = t
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (th *timerHandler) get(id int) *timer {
|
||||
th.mu.RLock()
|
||||
defer th.mu.RUnlock()
|
||||
|
||||
return th.timers[id]
|
||||
}
|
||||
|
||||
func (th *timerHandler) luaCreate(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.CheckNArgs(3); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timerTypInt, err := c.IntArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms, err := c.IntArg(1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cb, err := c.ClosureArg(2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timerTyp := timerType(timerTypInt)
|
||||
tmr := th.create(timerTyp, time.Duration(ms) * time.Millisecond, cb)
|
||||
return c.PushingNext1(t.Runtime, tmr.lua()), nil
|
||||
}
|
||||
|
||||
func (th *timerHandler) luaGet(thr *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||
if err := c.Check1Arg(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := c.IntArg(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := th.get(int(id))
|
||||
if t != nil {
|
||||
return c.PushingNext1(thr.Runtime, t.lua()), nil
|
||||
}
|
||||
|
||||
return c.Next(), nil
|
||||
}
|
||||
|
||||
func (th *timerHandler) loader(rtm *rt.Runtime) *rt.Table {
|
||||
thExports := map[string]util.LuaExport{
|
||||
"create": {th.luaCreate, 3, false},
|
||||
"get": {th.luaGet, 1, false},
|
||||
}
|
||||
|
||||
luaTh := rt.NewTable()
|
||||
util.SetExports(rtm, luaTh, thExports)
|
||||
|
||||
return luaTh
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
// LuaExport represents a Go function which can be exported to Lua.
|
||||
type LuaExport struct {
|
||||
Function rt.GoFunctionFunc
|
||||
ArgNum int
|
||||
Variadic bool
|
||||
}
|
||||
|
||||
// SetExports puts the Lua function exports in the table.
|
||||
func SetExports(rtm *rt.Runtime, tbl *rt.Table, exports map[string]LuaExport) {
|
||||
for name, export := range exports {
|
||||
rtm.SetEnvGoFunc(tbl, name, export.Function, export.ArgNum, export.Variadic)
|
||||
}
|
||||
}
|
122
util/util.go
122
util/util.go
|
@ -1,32 +1,120 @@
|
|||
package util
|
||||
|
||||
import "github.com/yuin/gopher-lua"
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
rt "github.com/arnodel/golua/runtime"
|
||||
)
|
||||
|
||||
// Document adds a documentation string to a module.
|
||||
// It is accessible via the __doc metatable.
|
||||
func Document(L *lua.LState, module lua.LValue, doc string) {
|
||||
mt := L.GetMetatable(module)
|
||||
if mt == lua.LNil {
|
||||
mt = L.NewTable()
|
||||
L.SetMetatable(module, mt)
|
||||
func Document(module *rt.Table, doc string) {
|
||||
mt := module.Metatable()
|
||||
|
||||
if mt == nil {
|
||||
mt = rt.NewTable()
|
||||
module.SetMetatable(mt)
|
||||
}
|
||||
L.SetField(mt, "__doc", lua.LString(doc))
|
||||
|
||||
mt.Set(rt.StringValue("__doc"), rt.StringValue(doc))
|
||||
}
|
||||
|
||||
// SetField sets a field in a table, adding docs for it.
|
||||
// It is accessible via the __docProp metatable. It is a table of the names of the fields.
|
||||
func SetField(L *lua.LState, module lua.LValue, field string, value lua.LValue, doc string) {
|
||||
mt := L.GetMetatable(module)
|
||||
if mt == lua.LNil {
|
||||
mt = L.NewTable()
|
||||
docProp := L.NewTable()
|
||||
L.SetField(mt, "__docProp", docProp)
|
||||
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value, doc string) {
|
||||
// TODO: ^ rtm isnt needed, i should remove it
|
||||
mt := module.Metatable()
|
||||
|
||||
if mt == nil {
|
||||
mt = rt.NewTable()
|
||||
docProp := rt.NewTable()
|
||||
mt.Set(rt.StringValue("__docProp"), rt.TableValue(docProp))
|
||||
|
||||
L.SetMetatable(module, mt)
|
||||
module.SetMetatable(mt)
|
||||
}
|
||||
docProp := L.GetTable(mt, lua.LString("__docProp"))
|
||||
docProp := mt.Get(rt.StringValue("__docProp"))
|
||||
|
||||
L.SetField(docProp, field, lua.LString(doc))
|
||||
L.SetField(module, field, value)
|
||||
docProp.AsTable().Set(rt.StringValue(field), rt.StringValue(doc))
|
||||
module.Set(rt.StringValue(field), value)
|
||||
}
|
||||
|
||||
// DoString runs the code string in the Lua runtime.
|
||||
func DoString(rtm *rt.Runtime, code string) error {
|
||||
chunk, err := rtm.CompileAndLoadLuaChunk("<string>", []byte(code), rt.TableValue(rtm.GlobalEnv()))
|
||||
if chunk != nil {
|
||||
_, err = rt.Call1(rtm.MainThread(), rt.FunctionValue(chunk))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DoFile runs the contents of the file in the Lua runtime.
|
||||
func DoFile(rtm *rt.Runtime, path string) error {
|
||||
f, err := os.Open(path)
|
||||
defer f.Close()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(f)
|
||||
c, err := reader.ReadByte()
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
// unread so a char won't be missing
|
||||
err = reader.UnreadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
if c == byte('#') {
|
||||
// shebang - skip that line
|
||||
_, err := reader.ReadBytes('\n')
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
buf = []byte{'\n'}
|
||||
}
|
||||
|
||||
for {
|
||||
line, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
buf = append(buf, line...)
|
||||
}
|
||||
|
||||
clos, err := rtm.LoadFromSourceOrCode(path, buf, "bt", rt.TableValue(rtm.GlobalEnv()), false)
|
||||
if clos != nil {
|
||||
_, err = rt.Call1(rtm.MainThread(), rt.FunctionValue(clos))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// HandleStrCallback handles function parameters for Go functions which take
|
||||
// a string and a closure.
|
||||
func HandleStrCallback(t *rt.Thread, c *rt.GoCont) (string, *rt.Closure, error) {
|
||||
if err := c.CheckNArgs(2); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
name, err := c.StringArg(0)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
cb, err := c.ClosureArg(1)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
return name, cb, err
|
||||
}
|
||||
|
|
3
vars.go
3
vars.go
|
@ -2,8 +2,7 @@ package main
|
|||
|
||||
// String vars that are free to be changed at compile time
|
||||
var (
|
||||
version = "v1.0.4"
|
||||
defaultConfDir = "" // ~ will be substituted for home, path for user's default config
|
||||
version = "v2.0.0"
|
||||
defaultHistDir = ""
|
||||
commonRequirePaths = "';./libs/?/init.lua;./?/init.lua;./?/?.lua'"
|
||||
|
||||
|
|
|
@ -17,4 +17,5 @@ var (
|
|||
dataDir = "/usr/local/share/hilbish"
|
||||
preloadPath = dataDir + "/prelude/init.lua"
|
||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
||||
defaultConfDir = getenv("XDG_CONFIG_HOME", "~/.config")
|
||||
)
|
||||
|
|
|
@ -17,4 +17,5 @@ var (
|
|||
dataDir = "/usr/share/hilbish"
|
||||
preloadPath = dataDir + "/prelude/init.lua"
|
||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
||||
defaultConfDir = ""
|
||||
)
|
||||
|
|
|
@ -11,4 +11,5 @@ var (
|
|||
dataDir = "~\\Appdata\\Roaming\\Hilbish" // ~ and \ gonna cry?
|
||||
preloadPath = dataDir + "\\prelude\\init.lua"
|
||||
sampleConfPath = dataDir + "\\hilbishrc.lua" // Path to default/sample config
|
||||
defaultConfDir = ""
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue