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 |
|
@ -20,8 +20,6 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
|
|
42
CHANGELOG.md
42
CHANGELOG.md
|
@ -1,25 +1,56 @@
|
||||||
# 🎀 Changelog
|
# 🎀 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
|
### Fixed
|
||||||
- Panic when history directory doesn't exist
|
- Panic when history directory doesn't exist
|
||||||
|
|
||||||
## [1.0.3] - 2021-03-12
|
## [1.0.3] - 2022-03-12
|
||||||
### Fixed
|
### Fixed
|
||||||
- Removed duplicate executable suggestions
|
- Removed duplicate executable suggestions
|
||||||
- User input is added to history now instead of what's ran by Hilbish
|
- User input is added to history now instead of what's ran by Hilbish
|
||||||
- Formatting issue with prompt on no input
|
- Formatting issue with prompt on no input
|
||||||
|
|
||||||
## [1.0.2] - 2021-03-06
|
## [1.0.2] - 2022-03-06
|
||||||
### Fixed
|
### Fixed
|
||||||
- Cases where Hilbish's history directory doesn't exist will no longer cause a panic
|
- 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
|
### Fixed
|
||||||
- Using `hilbish.appendPath` will no longer result in string spam (debugging thing left being)
|
- Using `hilbish.appendPath` will no longer result in string spam (debugging thing left being)
|
||||||
- Prompt gets set properly on startup
|
- Prompt gets set properly on startup
|
||||||
|
|
||||||
## [1.0.0] - 2021-03-06
|
## [1.0.0] - 2022-03-06
|
||||||
### Added
|
### Added
|
||||||
- MacOS is now officialy supported, default compile time vars have been added
|
- MacOS is now officialy supported, default compile time vars have been added
|
||||||
for it
|
for it
|
||||||
|
@ -392,6 +423,7 @@ This input for example will prompt for more input to complete:
|
||||||
|
|
||||||
First "stable" release of Hilbish.
|
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.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.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
|
[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
|
PREFIX ?= /usr
|
||||||
DESTDIR ?=
|
|
||||||
BINDIR ?= $(PREFIX)/bin
|
BINDIR ?= $(PREFIX)/bin
|
||||||
LIBDIR ?= $(PREFIX)/share/hilbish
|
LIBDIR ?= $(PREFIX)/share/hilbish
|
||||||
|
|
||||||
build:
|
MY_GOFLAGS = -ldflags "-s -w"
|
||||||
@go build -ldflags "-s -w"
|
|
||||||
|
|
||||||
dev:
|
all: dev
|
||||||
@go build -ldflags "-s -w -X main.version=$(shell git describe --tags)"
|
|
||||||
|
dev: MY_GOFLAGS = -ldflags "-s -w -X main.version=$(shell git describe --tags)"
|
||||||
|
dev: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build $(MY_GOFLAGS)
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
|
install -v -d "$(DESTDIR)$(BINDIR)/" && install -m 0755 -v hilbish "$(DESTDIR)$(BINDIR)/hilbish"
|
||||||
@mkdir -p "$(DESTDIR)$(LIBDIR)"
|
mkdir -p "$(DESTDIR)$(LIBDIR)"
|
||||||
@cp libs docs emmyLuaDocs prelude .hilbishrc.lua "$(DESTDIR)$(LIBDIR)" -r
|
cp -r libs docs emmyLuaDocs prelude .hilbishrc.lua "$(DESTDIR)$(LIBDIR)"
|
||||||
@grep "$(DESTDIR)$(BINDIR)/hilbish" -qxF /etc/shells || echo "$(DESTDIR)$(BINDIR)/hilbish" >> /etc/shells
|
grep -qxF "$(DESTDIR)$(BINDIR)/hilbish" /etc/shells || echo "$(DESTDIR)$(BINDIR)/hilbish" >> /etc/shells
|
||||||
@echo "Hilbish Installed"
|
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
@rm -vrf \
|
rm -vrf \
|
||||||
"$(DESTDIR)$(BINDIR)/hilbish" \
|
"$(DESTDIR)$(BINDIR)/hilbish" \
|
||||||
"$(DESTDIR)$(LIBDIR)"
|
"$(DESTDIR)$(LIBDIR)"
|
||||||
@sed -i '/hilbish/d' /etc/shells
|
sed -i '/hilbish/d' /etc/shells
|
||||||
@echo "Hilbish Uninstalled"
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@go clean
|
go clean
|
||||||
|
|
||||||
all: build install
|
.PHONY: all dev build install uninstall clean
|
||||||
|
|
||||||
.PHONY: install uninstall build dev clean
|
|
||||||
|
|
51
README.md
51
README.md
|
@ -2,7 +2,7 @@
|
||||||
<img src="./assets/hilbish-flower.png" width=128><br>
|
<img src="./assets/hilbish-flower.png" width=128><br>
|
||||||
<img src="./assets/hilbish-text.png" width=256><br>
|
<img src="./assets/hilbish-text.png" width=256><br>
|
||||||
<blockquote>
|
<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>
|
</blockquote>
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/Rosettea/Hilbish?style=flat-square">
|
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/Rosettea/Hilbish?style=flat-square">
|
||||||
|
@ -14,12 +14,25 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Hilbish is a Unix-y shell which uses Lua for scripting. Things like the prompt,
|
Hilbish is a extensible shell (framework). It was made to be very customizable
|
||||||
general configuration and such are done with Lua.
|
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
|
The motivation for choosing Lua was that its simpler and better to use
|
||||||
platforms Hilbish can be compiled for. It can also act as a Lua REPL if you want
|
than old shell script. It's fine for basic interactive shell uses,
|
||||||
it to be.
|
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
|
# Screenshots
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
@ -29,8 +42,6 @@ it to be.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
**NOTE:** Hilbish is currently only officially supported and tested on Linux
|
|
||||||
|
|
||||||
## Prebuilt binaries
|
## Prebuilt binaries
|
||||||
Go [here](https://nightly.link/Rosettea/Hilbish/workflows/build/master) for
|
Go [here](https://nightly.link/Rosettea/Hilbish/workflows/build/master) for
|
||||||
builds on the master branch.
|
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
|
### Prerequisites
|
||||||
- [Go 1.17+](https://go.dev)
|
- [Go 1.17+](https://go.dev)
|
||||||
|
|
||||||
#### Build
|
### Build
|
||||||
First, clone Hilbish. The recursive is required, as some Lua libraries
|
First, clone Hilbish. The recursive is required, as some Lua libraries
|
||||||
are submodules.
|
are submodules.
|
||||||
```sh
|
```sh
|
||||||
|
@ -78,13 +89,27 @@ make build
|
||||||
|
|
||||||
After you did all that, run `sudo make install` to install Hilbish globally.
|
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
|
# Contributing
|
||||||
Any kind of contributions to Hilbish are welcome!
|
Any kind of contributions are welcome! Hilbish is very easy to contribute to.
|
||||||
Read [CONTRIBUTING.md](CONTRIBUTING.md) before getting started.
|
Read [CONTRIBUTING.md](CONTRIBUTING.md) as a guideline to doing so.
|
||||||
|
|
||||||
**Thanks to everyone below who's contributed!**
|
**Thanks to everyone below who's contributed!**
|
||||||
<a href="https://github.com/Hilbis/Hilbish/graphs/contributors">
|
<a href="https://github.com/Rosettea/Hilbish/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=Hilbis/Hilbish" />
|
<img src="https://contrib.rocks/image?repo=Rosettea/Hilbish" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
*Made with [contributors-img](https://contrib.rocks).*
|
*Made with [contributors-img](https://contrib.rocks).*
|
||||||
|
|
93
aliases.go
93
aliases.go
|
@ -4,57 +4,59 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"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
|
aliases map[string]string
|
||||||
mu *sync.RWMutex
|
mu *sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize aliases map
|
// initialize aliases map
|
||||||
func NewAliases() *hilbishAliases {
|
func newAliases() *aliasHandler {
|
||||||
return &hilbishAliases{
|
return &aliasHandler{
|
||||||
aliases: make(map[string]string),
|
aliases: make(map[string]string),
|
||||||
mu: &sync.RWMutex{},
|
mu: &sync.RWMutex{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) Add(alias, cmd string) {
|
func (a *aliasHandler) Add(alias, cmd string) {
|
||||||
h.mu.Lock()
|
a.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
h.aliases[alias] = cmd
|
a.aliases[alias] = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) All() map[string]string {
|
func (a *aliasHandler) All() map[string]string {
|
||||||
return h.aliases
|
return a.aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) Delete(alias string) {
|
func (a *aliasHandler) Delete(alias string) {
|
||||||
h.mu.Lock()
|
a.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer a.mu.Unlock()
|
||||||
|
|
||||||
delete(h.aliases, alias)
|
delete(a.aliases, alias)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) Resolve(cmdstr string) string {
|
func (a *aliasHandler) Resolve(cmdstr string) string {
|
||||||
h.mu.RLock()
|
a.mu.RLock()
|
||||||
defer h.mu.RUnlock()
|
defer a.mu.RUnlock()
|
||||||
|
|
||||||
args := strings.Split(cmdstr, " ")
|
args := strings.Split(cmdstr, " ")
|
||||||
for h.aliases[args[0]] != "" {
|
for a.aliases[args[0]] != "" {
|
||||||
alias := h.aliases[args[0]]
|
alias := a.aliases[args[0]]
|
||||||
cmdstr = alias + strings.TrimPrefix(cmdstr, args[0])
|
cmdstr = alias + strings.TrimPrefix(cmdstr, args[0])
|
||||||
cmdArgs, _ := splitInput(cmdstr)
|
cmdArgs, _ := splitInput(cmdstr)
|
||||||
args = cmdArgs
|
args = cmdArgs
|
||||||
|
|
||||||
if h.aliases[args[0]] == alias {
|
if a.aliases[args[0]] == alias {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if h.aliases[args[0]] != "" {
|
if a.aliases[args[0]] != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,41 +66,38 @@ func (h *hilbishAliases) Resolve(cmdstr string) string {
|
||||||
|
|
||||||
// lua section
|
// 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
|
// create a lua module with our functions
|
||||||
hshaliasesLua := map[string]lua.LGFunction{
|
hshaliasesLua := map[string]util.LuaExport{
|
||||||
"add": h.luaAdd,
|
"add": util.LuaExport{hlalias, 2, false},
|
||||||
"list": h.luaList,
|
"list": util.LuaExport{a.luaList, 0, false},
|
||||||
"del": h.luaDelete,
|
"del": util.LuaExport{a.luaDelete, 1, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := L.SetFuncs(L.NewTable(), hshaliasesLua)
|
mod := rt.NewTable()
|
||||||
|
util.SetExports(rtm, mod, hshaliasesLua)
|
||||||
|
|
||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) luaAdd(L *lua.LState) int {
|
func (a *aliasHandler) luaList(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
alias := L.CheckString(1)
|
aliasesList := rt.NewTable()
|
||||||
cmd := L.CheckString(2)
|
for k, v := range a.All() {
|
||||||
h.Add(alias, cmd)
|
aliasesList.Set(rt.StringValue(k), rt.StringValue(v))
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
L.Push(aliasesList)
|
return c.PushingNext1(t.Runtime, rt.TableValue(aliasesList)), nil
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hilbishAliases) luaDelete(L *lua.LState) int {
|
func (a *aliasHandler) luaDelete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
alias := L.CheckString(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
h.Delete(alias)
|
return nil, err
|
||||||
|
}
|
||||||
|
alias, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
a.Delete(alias)
|
||||||
|
|
||||||
return 0
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
716
api.go
716
api.go
|
@ -4,6 +4,8 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -14,182 +16,128 @@ import (
|
||||||
|
|
||||||
"hilbish/util"
|
"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/maxlandon/readline"
|
||||||
"github.com/blackfireio/osinfo"
|
"github.com/blackfireio/osinfo"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exports = map[string]lua.LGFunction {
|
var exports = map[string]util.LuaExport{
|
||||||
"alias": hlalias,
|
"alias": {hlalias, 2, false},
|
||||||
"appendPath": hlappendPath,
|
"appendPath": {hlappendPath, 1, false},
|
||||||
"complete": hlcomplete,
|
"complete": {hlcomplete, 2, false},
|
||||||
"cwd": hlcwd,
|
"cwd": {hlcwd, 0, false},
|
||||||
"exec": hlexec,
|
"exec": {hlexec, 1, false},
|
||||||
"goro": hlgoro,
|
"runnerMode": {hlrunnerMode, 1, false},
|
||||||
"multiprompt": hlmlprompt,
|
"goro": {hlgoro, 1, true},
|
||||||
"prependPath": hlprependPath,
|
"highlighter": {hlhighlighter, 1, false},
|
||||||
"prompt": hlprompt,
|
"hinter": {hlhinter, 1, false},
|
||||||
"inputMode": hlinputMode,
|
"multiprompt": {hlmultiprompt, 1, false},
|
||||||
"interval": hlinterval,
|
"prependPath": {hlprependPath, 1, false},
|
||||||
"read": hlread,
|
"prompt": {hlprompt, 1, true},
|
||||||
"run": hlrun,
|
"inputMode": {hlinputMode, 1, false},
|
||||||
"timeout": hltimeout,
|
"interval": {hlinterval, 2, false},
|
||||||
"which": hlwhich,
|
"read": {hlread, 1, false},
|
||||||
|
"run": {hlrun, 1, true},
|
||||||
|
"timeout": {hltimeout, 2, false},
|
||||||
|
"which": {hlwhich, 1, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
var greeting string
|
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 {
|
func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
mod := L.SetFuncs(L.NewTable(), exports)
|
mod := rt.NewTable()
|
||||||
|
util.SetExports(rtm, mod, exports)
|
||||||
hshMod = mod
|
hshMod = mod
|
||||||
|
|
||||||
host, _ := os.Hostname()
|
host, _ := os.Hostname()
|
||||||
username := curuser.Username
|
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" {
|
if runtime.GOOS == "windows" {
|
||||||
username = strings.Split(username, "\\")[1] // for some reason Username includes the hostname on 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")
|
greeting = `Welcome to {magenta}Hilbish{reset}, {cyan}` + username + `{reset}.
|
||||||
util.SetField(L, mod, "user", lua.LString(username), "Username of user")
|
The nice lil shell for {blue}Lua{reset} fanatics!
|
||||||
util.SetField(L, mod, "host", lua.LString(host), "Host name of the machine")
|
Check out the {blue}{bold}guide{reset} command to get started.
|
||||||
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(rtm, mod, "ver", rt.StringValue(version), "Hilbish version")
|
||||||
util.SetField(L, mod, "interactive", lua.LBool(interactive), "If this is an interactive shell")
|
util.SetField(rtm, mod, "user", rt.StringValue(username), "Username of user")
|
||||||
util.SetField(L, mod, "login", lua.LBool(interactive), "Whether this is a login shell")
|
util.SetField(rtm, mod, "host", rt.StringValue(host), "Host name of the machine")
|
||||||
util.SetField(L, mod, "greeting", lua.LString(greeting), "Hilbish's welcome message for interactive shells. It has Lunacolors formatting.")
|
util.SetField(rtm, mod, "home", rt.StringValue(curuser.HomeDir), "Home directory of the user")
|
||||||
util.SetField(l, mod, "vimMode", lua.LNil, "Current Vim mode of Hilbish (nil if not in Vim mode)")
|
util.SetField(rtm, mod, "dataDir", rt.StringValue(dataDir), "Directory for Hilbish's data files")
|
||||||
util.SetField(l, hshMod, "exitCode", lua.LNumber(0), "Exit code of last exected command")
|
util.SetField(rtm, mod, "interactive", rt.BoolValue(interactive), "If this is an interactive shell")
|
||||||
util.Document(L, mod, "Hilbish's core API, containing submodules and functions which relate to the shell itself.")
|
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
|
// hilbish.userDir table
|
||||||
hshuser := L.NewTable()
|
hshuser := rt.NewTable()
|
||||||
|
|
||||||
util.SetField(L, hshuser, "config", lua.LString(confDir), "User's config directory")
|
util.SetField(rtm, hshuser, "config", rt.StringValue(confDir), "User's config directory")
|
||||||
util.SetField(L, hshuser, "data", lua.LString(userDataDir), "XDG data directory")
|
util.SetField(rtm, hshuser, "data", rt.StringValue(userDataDir), "XDG data directory")
|
||||||
util.Document(L, hshuser, "User directories to store configs and/or modules.")
|
util.Document(hshuser, "User directories to store configs and/or modules.")
|
||||||
L.SetField(mod, "userDir", hshuser)
|
mod.Set(rt.StringValue("userDir"), rt.TableValue(hshuser))
|
||||||
|
|
||||||
// hilbish.os table
|
// hilbish.os table
|
||||||
hshos := L.NewTable()
|
hshos := rt.NewTable()
|
||||||
info, _ := osinfo.GetOSInfo()
|
info, _ := osinfo.GetOSInfo()
|
||||||
|
|
||||||
util.SetField(L, hshos, "family", lua.LString(info.Family), "Family name of the current OS")
|
util.SetField(rtm, hshos, "family", rt.StringValue(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(rtm, hshos, "name", rt.StringValue(info.Name), "Pretty name of the current OS")
|
||||||
util.SetField(L, hshos, "version", lua.LString(info.Version), "Version of the current OS")
|
util.SetField(rtm, hshos, "version", rt.StringValue(info.Version), "Version of the current OS")
|
||||||
util.Document(L, hshos, "OS info interface")
|
util.Document(hshos, "OS info interface")
|
||||||
L.SetField(mod, "os", hshos)
|
mod.Set(rt.StringValue("os"), rt.TableValue(hshos))
|
||||||
|
|
||||||
// hilbish.aliases table
|
// hilbish.aliases table
|
||||||
aliases = NewAliases()
|
aliases = newAliases()
|
||||||
aliasesModule := aliases.Loader(L)
|
aliasesModule := aliases.Loader(rtm)
|
||||||
util.Document(L, aliasesModule, "Alias inferface for Hilbish.")
|
util.Document(aliasesModule, "Alias inferface for Hilbish.")
|
||||||
L.SetField(mod, "aliases", aliasesModule)
|
mod.Set(rt.StringValue("aliases"), rt.TableValue(aliasesModule))
|
||||||
|
|
||||||
// hilbish.history table
|
// hilbish.history table
|
||||||
historyModule := lr.Loader(L)
|
historyModule := lr.Loader(rtm)
|
||||||
util.Document(L, historyModule, "History interface for Hilbish.")
|
mod.Set(rt.StringValue("history"), rt.TableValue(historyModule))
|
||||||
L.SetField(mod, "history", historyModule)
|
util.Document(historyModule, "History interface for Hilbish.")
|
||||||
|
|
||||||
// hilbish.completions table
|
// hilbish.completion table
|
||||||
hshcomp := L.NewTable()
|
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(rtm, hshcomp, "bins",
|
||||||
util.SetField(L, hshcomp, "bins", L.NewFunction(luaBinaryComplete), "Completer for executables/binaries")
|
rt.FunctionValue(rt.NewGoFunction(luaBinaryComplete, "bins", 3, false)),
|
||||||
util.Document(L, hshcomp, "Completions interface for Hilbish.")
|
"Completer for executables/binaries")
|
||||||
L.SetField(mod, "completion", hshcomp)
|
|
||||||
|
|
||||||
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 {
|
// hilbish.jobs table
|
||||||
query := L.CheckString(1)
|
jobs = newJobHandler()
|
||||||
ctx := L.CheckString(2)
|
jobModule := jobs.loader(rtm)
|
||||||
fields := L.CheckTable(3)
|
util.Document(jobModule, "(Background) job interface.")
|
||||||
|
mod.Set(rt.StringValue("jobs"), rt.TableValue(jobModule))
|
||||||
|
|
||||||
var fds []string
|
timers = newTimerHandler()
|
||||||
fields.ForEach(func(k lua.LValue, v lua.LValue) {
|
timerModule := timers.loader(rtm)
|
||||||
fds = append(fds, v.String())
|
util.Document(timerModule, "Timer interface, for control of all intervals and timeouts.")
|
||||||
})
|
mod.Set(rt.StringValue("timers"), rt.TableValue(timerModule))
|
||||||
|
|
||||||
completions := fileComplete(query, ctx, fds)
|
return rt.TableValue(mod), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getenv(key, fallback string) string {
|
func getenv(key, fallback string) string {
|
||||||
|
@ -200,28 +148,164 @@ func getenv(key, fallback string) string {
|
||||||
return value
|
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(prompt) -> input?
|
||||||
// Read input from the user, using Hilbish's line editor/input reader.
|
// Read input from the user, using Hilbish's line editor/input reader.
|
||||||
// This is a separate instance from the one Hilbish actually uses.
|
// This is a separate instance from the one Hilbish actually uses.
|
||||||
// Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
|
// Returns `input`, will be nil if ctrl + d is pressed, or an error occurs (which shouldn't happen)
|
||||||
// --- @param prompt string
|
// --- @param prompt string
|
||||||
func hlread(L *lua.LState) int {
|
func hlread(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
luaprompt := L.CheckString(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
luaprompt, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
lualr := newLineReader("", true)
|
lualr := newLineReader("", true)
|
||||||
lualr.SetPrompt(luaprompt)
|
lualr.SetPrompt(luaprompt)
|
||||||
|
|
||||||
input, err := lualr.Read()
|
input, err := lualr.Read()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
L.Push(lua.LNil)
|
return c.Next(), nil
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
L.Push(lua.LString(input))
|
return c.PushingNext1(t.Runtime, rt.StringValue(input)), nil
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
prompt(str)
|
prompt(str, typ?)
|
||||||
Changes the shell prompt to `str`
|
Changes the shell prompt to `str`
|
||||||
There are a few verbs that can be used in the prompt text.
|
There are a few verbs that can be used in the prompt text.
|
||||||
These will be formatted and replaced with the appropriate values.
|
These will be formatted and replaced with the appropriate values.
|
||||||
|
@ -229,53 +313,110 @@ These will be formatted and replaced with the appropriate values.
|
||||||
`%u` - Name of current user
|
`%u` - Name of current user
|
||||||
`%h` - Hostname of device
|
`%h` - Hostname of device
|
||||||
--- @param str string
|
--- @param str string
|
||||||
|
--- @param typ string Type of prompt, being left or right. Left by default.
|
||||||
*/
|
*/
|
||||||
func hlprompt(L *lua.LState) int {
|
func hlprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
prompt = L.CheckString(1)
|
err := c.Check1Arg()
|
||||||
lr.SetPrompt(fmtPrompt(prompt))
|
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)
|
// multiprompt(str)
|
||||||
// Changes the continued line prompt to `str`
|
// Changes the continued line prompt to `str`
|
||||||
// --- @param str string
|
// --- @param str string
|
||||||
func hlmlprompt(L *lua.LState) int {
|
func hlmultiprompt(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
multilinePrompt = L.CheckString(1)
|
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)
|
// alias(cmd, orig)
|
||||||
// Sets an alias of `orig` to `cmd`
|
// Sets an alias of `cmd` to `orig`
|
||||||
// --- @param cmd string
|
// --- @param cmd string
|
||||||
// --- @param orig string
|
// --- @param orig string
|
||||||
func hlalias(L *lua.LState) int {
|
func hlalias(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
alias := L.CheckString(1)
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
source := L.CheckString(2)
|
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)
|
// appendPath(dir)
|
||||||
// Appends `dir` to $PATH
|
// Appends `dir` to $PATH
|
||||||
// --- @param dir string|table
|
// --- @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
|
// check if dir is a table or a string
|
||||||
arg := L.Get(1)
|
if arg.Type() == rt.TableType {
|
||||||
if arg.Type() == lua.LTTable {
|
nextVal := rt.NilValue
|
||||||
arg.(*lua.LTable).ForEach(func(k lua.LValue, v lua.LValue) {
|
for {
|
||||||
appendPath(v.String())
|
next, val, ok := arg.AsTable().Next(nextVal)
|
||||||
})
|
if next == rt.NilValue {
|
||||||
} else if arg.Type() == lua.LTString {
|
break
|
||||||
appendPath(arg.String())
|
}
|
||||||
|
nextVal = next
|
||||||
|
|
||||||
|
valStr, ok := val.TryString()
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
appendPath(valStr)
|
||||||
|
}
|
||||||
|
} else if arg.Type() == rt.StringType {
|
||||||
|
appendPath(arg.AsString())
|
||||||
} else {
|
} 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) {
|
func appendPath(dir string) {
|
||||||
|
@ -291,8 +432,14 @@ func appendPath(dir string) {
|
||||||
// exec(cmd)
|
// exec(cmd)
|
||||||
// Replaces running hilbish with `cmd`
|
// Replaces running hilbish with `cmd`
|
||||||
// --- @param cmd string
|
// --- @param cmd string
|
||||||
func hlexec(L *lua.LState) int {
|
func hlexec(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
cmd := L.CheckString(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
cmdArgs, _ := splitInput(cmd)
|
cmdArgs, _ := splitInput(cmd)
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
cmdPath, err := exec.LookPath(cmdArgs[0])
|
cmdPath, err := exec.LookPath(cmdArgs[0])
|
||||||
|
@ -314,88 +461,82 @@ func hlexec(L *lua.LState) int {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// goro(fn)
|
// goro(fn)
|
||||||
// Puts `fn` in a goroutine
|
// Puts `fn` in a goroutine
|
||||||
// --- @param fn function
|
// --- @param fn function
|
||||||
func hlgoro(L *lua.LState) int {
|
func hlgoro(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
fn := L.CheckFunction(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
argnum := L.GetTop()
|
return nil, err
|
||||||
args := make([]lua.LValue, argnum)
|
}
|
||||||
for i := 1; i <= argnum; i++ {
|
fn, err := c.ClosureArg(0)
|
||||||
args[i - 1] = L.Get(i)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// call fn
|
// call fn
|
||||||
go func() {
|
go func() {
|
||||||
if err := L.CallByParam(lua.P{
|
_, err := rt.Call1(l.MainThread(), rt.FunctionValue(fn), c.Etc()...)
|
||||||
Fn: fn,
|
if err != nil {
|
||||||
NRet: 0,
|
|
||||||
Protect: true,
|
|
||||||
}, args...); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Error in goro function:\n\n", err)
|
fmt.Fprintln(os.Stderr, "Error in goro function:\n\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return 0
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// timeout(cb, time)
|
// timeout(cb, time)
|
||||||
// Runs the `cb` function after `time` in milliseconds
|
// Runs the `cb` function after `time` in milliseconds
|
||||||
|
// Returns a `timer` object (see `doc timers`).
|
||||||
// --- @param cb function
|
// --- @param cb function
|
||||||
// --- @param time number
|
// --- @param time number
|
||||||
func hltimeout(L *lua.LState) int {
|
// --- @return table
|
||||||
cb := L.CheckFunction(1)
|
func hltimeout(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
ms := L.CheckInt(2)
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
|
return nil, err
|
||||||
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 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)
|
// 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 cb function
|
||||||
// --- @param time number
|
// --- @param time number
|
||||||
func hlinterval(L *lua.LState) int {
|
// --- @return table
|
||||||
intervalfunc := L.CheckFunction(1)
|
func hlinterval(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
ms := L.CheckInt(2)
|
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
|
interval := time.Duration(ms) * time.Millisecond
|
||||||
|
timer := timers.create(timerInterval, interval, cb)
|
||||||
|
timer.start()
|
||||||
|
|
||||||
ticker := time.NewTicker(interval)
|
return c.PushingNext1(t.Runtime, timer.lua()), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// complete(scope, cb)
|
// complete(scope, cb)
|
||||||
|
@ -408,20 +549,27 @@ func hlinterval(L *lua.LState) int {
|
||||||
// `grid` (the normal file completion display) or `list` (with a description)
|
// `grid` (the normal file completion display) or `list` (with a description)
|
||||||
// --- @param scope string
|
// --- @param scope string
|
||||||
// --- @param cb function
|
// --- @param cb function
|
||||||
func hlcomplete(L *lua.LState) int {
|
func hlcomplete(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
scope := L.CheckString(1)
|
scope, cb, err := util.HandleStrCallback(t, c)
|
||||||
cb := L.CheckFunction(2)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
luaCompletions[scope] = cb
|
luaCompletions[scope] = cb
|
||||||
|
|
||||||
return 0
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// prependPath(dir)
|
// prependPath(dir)
|
||||||
// Prepends `dir` to $PATH
|
// Prepends `dir` to $PATH
|
||||||
// --- @param dir string
|
// --- @param dir string
|
||||||
func hlprependPath(L *lua.LState) int {
|
func hlprependPath(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
dir := L.CheckString(1)
|
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)
|
dir = strings.Replace(dir, "~", curuser.HomeDir, 1)
|
||||||
pathenv := os.Getenv("PATH")
|
pathenv := os.Getenv("PATH")
|
||||||
|
|
||||||
|
@ -430,29 +578,40 @@ func hlprependPath(L *lua.LState) int {
|
||||||
os.Setenv("PATH", dir + string(os.PathListSeparator) + pathenv)
|
os.Setenv("PATH", dir + string(os.PathListSeparator) + pathenv)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// which(binName)
|
// which(binName)
|
||||||
// Searches for an executable called `binName` in the directories of $PATH
|
// Searches for an executable called `binName` in the directories of $PATH
|
||||||
// --- @param binName string
|
// --- @param binName string
|
||||||
func hlwhich(L *lua.LState) int {
|
func hlwhich(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
binName := L.CheckString(1)
|
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)
|
path, err := exec.LookPath(binName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Push(lua.LNil)
|
return c.Next(), nil
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Push(lua.LString(path))
|
return c.PushingNext1(t.Runtime, rt.StringValue(path)), nil
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// inputMode(mode)
|
// inputMode(mode)
|
||||||
// Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
// Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
||||||
// --- @param mode string
|
// --- @param mode string
|
||||||
func hlinputMode(L *lua.LState) int {
|
func hlinputMode(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
mode := L.CheckString(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mode, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
case "emacs":
|
case "emacs":
|
||||||
unsetVimMode()
|
unsetVimMode()
|
||||||
|
@ -460,7 +619,74 @@ func hlinputMode(L *lua.LState) int {
|
||||||
case "vim":
|
case "vim":
|
||||||
setVimMode("insert")
|
setVimMode("insert")
|
||||||
lr.rl.InputMode = readline.Vim
|
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" {
|
if emmyType == "@param" {
|
||||||
em.Params = append(em.Params, emmyLinePieces[1])
|
em.Params = append(em.Params, emmyLinePieces[1])
|
||||||
}
|
}
|
||||||
|
if emmyType == "@vararg" {
|
||||||
|
em.Params = append(em.Params, "...") // add vararg
|
||||||
|
}
|
||||||
em.Docs = append(em.Docs, d)
|
em.Docs = append(em.Docs, d)
|
||||||
} else {
|
} else {
|
||||||
funcdoc = append(funcdoc, d)
|
funcdoc = append(funcdoc, d)
|
||||||
|
@ -111,6 +114,9 @@ func main() {
|
||||||
if emmyType == "@param" {
|
if emmyType == "@param" {
|
||||||
em.Params = append(em.Params, emmyLinePieces[1])
|
em.Params = append(em.Params, emmyLinePieces[1])
|
||||||
}
|
}
|
||||||
|
if emmyType == "@vararg" {
|
||||||
|
em.Params = append(em.Params, "...") // add vararg
|
||||||
|
}
|
||||||
em.Docs = append(em.Docs, d)
|
em.Docs = append(em.Docs, d)
|
||||||
} else {
|
} else {
|
||||||
funcdoc = append(funcdoc, d)
|
funcdoc = append(funcdoc, d)
|
||||||
|
|
110
complete.go
110
complete.go
|
@ -2,27 +2,12 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"os"
|
"os"
|
||||||
"unicode"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func fileComplete(query, ctx string, fields []string) []string {
|
func fileComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
var completions []string
|
return matchPath(query)
|
||||||
|
|
||||||
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 binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
|
@ -31,17 +16,17 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
prefixes := []string{"./", "../", "/", "~/"}
|
prefixes := []string{"./", "../", "/", "~/"}
|
||||||
for _, prefix := range prefixes {
|
for _, prefix := range prefixes {
|
||||||
if strings.HasPrefix(query, prefix) {
|
if strings.HasPrefix(query, prefix) {
|
||||||
fileCompletions := fileComplete(query, ctx, fields)
|
fileCompletions, filePref := matchPath(query)
|
||||||
if len(fileCompletions) != 0 {
|
if len(fileCompletions) != 0 {
|
||||||
for _, f := range fileCompletions {
|
for _, f := range fileCompletions {
|
||||||
name := strings.Replace(query + f, "~", curuser.HomeDir, 1)
|
fullPath, _ := filepath.Abs(expandHome(query + strings.TrimPrefix(f, filePref)))
|
||||||
if info, err := os.Stat(name); err == nil && info.Mode().Perm() & 0100 == 0 {
|
if err := findExecutable(fullPath, false, true); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
completions = append(completions, f)
|
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
|
// get basename from matches
|
||||||
for _, match := range matches {
|
for _, match := range matches {
|
||||||
// check if we have execute permissions for our match
|
// 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
|
continue
|
||||||
}
|
}
|
||||||
// get basename from match
|
// get basename from match
|
||||||
|
@ -76,55 +62,53 @@ func binaryComplete(query, ctx string, fields []string) ([]string, string) {
|
||||||
return completions, query
|
return completions, query
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchPath(path, pref string) ([]string, error) {
|
func matchPath(query string) ([]string, string) {
|
||||||
var entries []string
|
var entries []string
|
||||||
matches, err := filepath.Glob(desensitize(path) + "*")
|
var baseName string
|
||||||
if err == nil {
|
|
||||||
args := []string{
|
|
||||||
"\"", "\\\"",
|
|
||||||
"'", "\\'",
|
|
||||||
"`", "\\`",
|
|
||||||
" ", "\\ ",
|
|
||||||
"(", "\\(",
|
|
||||||
")", "\\)",
|
|
||||||
"[", "\\[",
|
|
||||||
"]", "\\]",
|
|
||||||
}
|
|
||||||
|
|
||||||
r := strings.NewReplacer(args...)
|
path, _ := filepath.Abs(expandHome(filepath.Dir(query)))
|
||||||
for _, match := range matches {
|
if string(query) == "" {
|
||||||
name := filepath.Base(match)
|
// filepath base below would give us "."
|
||||||
p := filepath.Base(pref)
|
// which would cause a match of only dotfiles
|
||||||
if pref == "" {
|
path, _ = filepath.Abs(".")
|
||||||
p = ""
|
} 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)
|
entry = escapeFilename(entry)
|
||||||
matchFull, _ := filepath.Abs(match)
|
entries = append(entries, entry)
|
||||||
if info, err := os.Stat(matchFull); err == nil && info.IsDir() {
|
|
||||||
name = name + string(os.PathSeparator)
|
|
||||||
}
|
|
||||||
name = r.Replace(name)
|
|
||||||
entries = append(entries, name)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries, err
|
return entries, baseName
|
||||||
}
|
}
|
||||||
|
|
||||||
func desensitize(text string) string {
|
func escapeFilename(fname string) string {
|
||||||
if runtime.GOOS == "windows" {
|
args := []string{
|
||||||
return text
|
"\"", "\\\"",
|
||||||
|
"'", "\\'",
|
||||||
|
"`", "\\`",
|
||||||
|
" ", "\\ ",
|
||||||
|
"(", "\\(",
|
||||||
|
")", "\\)",
|
||||||
|
"[", "\\[",
|
||||||
|
"]", "\\]",
|
||||||
|
"$", "\\$",
|
||||||
|
"&", "\\&",
|
||||||
|
"*", "\\*",
|
||||||
|
">", "\\>",
|
||||||
|
"<", "\\<",
|
||||||
|
"|", "\\|",
|
||||||
}
|
}
|
||||||
|
|
||||||
p := strings.Builder{}
|
r := strings.NewReplacer(args...)
|
||||||
|
return r.Replace(fname)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
appendPath(dir) > Appends `dir` to $PATH
|
||||||
|
|
||||||
|
@ -16,15 +16,26 @@ exec(cmd) > Replaces running hilbish with `cmd`
|
||||||
|
|
||||||
goro(fn) > Puts `fn` in a goroutine
|
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
|
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`
|
multiprompt(str) > Changes the continued line prompt to `str`
|
||||||
|
|
||||||
prependPath(dir) > Prepends `dir` to $PATH
|
prependPath(dir) > Prepends `dir` to $PATH
|
||||||
|
|
||||||
prompt(str) > Changes the shell prompt to `str`
|
prompt(str, typ?) > Changes the shell prompt to `str`
|
||||||
There are a few verbs that can be used in the prompt text.
|
There are a few verbs that can be used in the prompt text.
|
||||||
These will be formatted and replaced with the appropriate values.
|
These will be formatted and replaced with the appropriate values.
|
||||||
`%d` - Current working directory
|
`%d` - Current working directory
|
||||||
|
@ -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.
|
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)
|
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
|
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
|
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
|
restoreState() > Restores the last saved state of the terminal
|
||||||
|
|
||||||
saveState() > Saves the current 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`
|
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
|
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`
|
--- Throws a hook with `name` with the provided `args`
|
||||||
--- @param name string
|
--- @param name string
|
||||||
--- @vararg any
|
--- @vararg any
|
||||||
function bait.throw(name) end
|
function bait.throw(name, ...) end
|
||||||
|
|
||||||
return bait
|
return bait
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
local hilbish = {}
|
local hilbish = {}
|
||||||
|
|
||||||
--- Sets an alias of `orig` to `cmd`
|
--- Sets an alias of `cmd` to `orig`
|
||||||
--- @param cmd string
|
--- @param cmd string
|
||||||
--- @param orig string
|
--- @param orig string
|
||||||
function hilbish.alias(cmd, orig) end
|
function hilbish.alias(cmd, orig) end
|
||||||
|
@ -33,18 +33,34 @@ function hilbish.exec(cmd) end
|
||||||
--- @param fn function
|
--- @param fn function
|
||||||
function hilbish.goro(fn) end
|
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
|
--- Sets the input mode for Hilbish's line reader. Accepts either emacs for vim
|
||||||
--- @param mode string
|
--- @param mode string
|
||||||
function hilbish.inputMode(mode) end
|
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 cb function
|
||||||
--- @param time number
|
--- @param time number
|
||||||
|
--- @return table
|
||||||
function hilbish.interval(cb, time) end
|
function hilbish.interval(cb, time) end
|
||||||
|
|
||||||
--- Changes the continued line prompt to `str`
|
--- Changes the continued line prompt to `str`
|
||||||
--- @param str string
|
--- @param str string
|
||||||
function hilbish.mlprompt(str) end
|
function hilbish.multiprompt(str) end
|
||||||
|
|
||||||
--- Prepends `dir` to $PATH
|
--- Prepends `dir` to $PATH
|
||||||
--- @param dir string
|
--- @param dir string
|
||||||
|
@ -57,7 +73,8 @@ function hilbish.prependPath(dir) end
|
||||||
--- `%u` - Name of current user
|
--- `%u` - Name of current user
|
||||||
--- `%h` - Hostname of device
|
--- `%h` - Hostname of device
|
||||||
--- @param str string
|
--- @param str string
|
||||||
function hilbish.prompt(str) end
|
--- @param typ string Type of prompt, being left or right. Left by default.
|
||||||
|
function hilbish.prompt(str, typ) end
|
||||||
|
|
||||||
--- Read input from the user, using Hilbish's line editor/input reader.
|
--- Read input from the user, using Hilbish's line editor/input reader.
|
||||||
--- This is a separate instance from the one Hilbish actually uses.
|
--- This is a separate instance from the one Hilbish actually uses.
|
||||||
|
@ -66,12 +83,24 @@ function hilbish.prompt(str) end
|
||||||
function hilbish.read(prompt) end
|
function hilbish.read(prompt) end
|
||||||
|
|
||||||
--- Runs `cmd` in Hilbish's sh interpreter.
|
--- 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
|
--- @param cmd string
|
||||||
function hilbish.run(cmd) end
|
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
|
--- Runs the `cb` function after `time` in milliseconds
|
||||||
|
--- Returns a `timer` object (see `doc timers`).
|
||||||
--- @param cb function
|
--- @param cb function
|
||||||
--- @param time number
|
--- @param time number
|
||||||
|
--- @return table
|
||||||
function hilbish.timeout(cb, time) end
|
function hilbish.timeout(cb, time) end
|
||||||
|
|
||||||
--- Searches for an executable called `binName` in the directories of $PATH
|
--- Searches for an executable called `binName` in the directories of $PATH
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
local terminal = {}
|
local terminal = {}
|
||||||
|
|
||||||
--- Puts the terminal in raw mode
|
|
||||||
function terminal.raw() end
|
|
||||||
|
|
||||||
--- Restores the last saved state of the terminal
|
--- Restores the last saved state of the terminal
|
||||||
function terminal.restoreState() end
|
function terminal.restoreState() end
|
||||||
|
|
||||||
--- Saves the current state of the terminal
|
--- Saves the current state of the terminal
|
||||||
function terminal.saveState() end
|
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`
|
--- 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
|
--- Note: this is not the size in relation to the dimensions of the display
|
||||||
function terminal.size() end
|
function terminal.size() end
|
||||||
|
|
363
exec.go
363
exec.go
|
@ -1,30 +1,109 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os/exec"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
|
||||||
"github.com/yuin/gopher-lua"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
"mvdan.cc/sh/v3/shell"
|
"mvdan.cc/sh/v3/shell"
|
||||||
//"github.com/yuin/gopher-lua/parse"
|
//"github.com/yuin/gopher-lua/parse"
|
||||||
"mvdan.cc/sh/v3/interp"
|
"mvdan.cc/sh/v3/interp"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"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
|
running = true
|
||||||
cmdString := aliases.Resolve(input)
|
cmdString := aliases.Resolve(input)
|
||||||
|
|
||||||
hooks.Em.Emit("command.preexec", input, cmdString)
|
hooks.Em.Emit("command.preexec", input, cmdString)
|
||||||
|
|
||||||
|
var exitCode uint8
|
||||||
|
var err error
|
||||||
|
if runnerMode.Type() == rt.StringType {
|
||||||
|
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
|
// 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 {
|
if err != nil && noexecute {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
/* if lerr, ok := err.(*lua.ApiError); ok {
|
/* 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
|
// And if there's no syntax errors and -n isnt provided, run
|
||||||
if !noexecute {
|
if !noexecute {
|
||||||
l.Push(fn)
|
if chunk != nil {
|
||||||
err = l.PCall(0, lua.MultRet, nil)
|
_, err = rt.Call1(l.MainThread(), rt.FunctionValue(chunk))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
cmdFinish(0, cmdString, origInput)
|
return cmdString, 0, nil
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last option: use sh interpreter
|
return cmdString, 125, err
|
||||||
err = execCommand(cmdString, origInput)
|
}
|
||||||
|
|
||||||
|
func handleSh(cmdString string) (string, uint8, error) {
|
||||||
|
_, _, err := execCommand(cmdString, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If input is incomplete, start multiline prompting
|
// If input is incomplete, start multiline prompting
|
||||||
if syntax.IsIncomplete(err) {
|
if syntax.IsIncomplete(err) {
|
||||||
|
if !interactive {
|
||||||
|
return cmdString, 126, err
|
||||||
|
}
|
||||||
for {
|
for {
|
||||||
cmdString, err = continuePrompt(strings.TrimSuffix(cmdString, "\\"))
|
cmdString, err = continuePrompt(strings.TrimSuffix(cmdString, "\\"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
err = execCommand(cmdString, origInput)
|
_, _, err = execCommand(cmdString, true)
|
||||||
if syntax.IsIncomplete(err) || strings.HasSuffix(input, "\\") {
|
if syntax.IsIncomplete(err) || strings.HasSuffix(cmdString, "\\") {
|
||||||
continue
|
continue
|
||||||
} else if code, ok := interp.IsExitStatus(err); ok {
|
} else if code, ok := interp.IsExitStatus(err); ok {
|
||||||
cmdFinish(code, cmdString, origInput)
|
return cmdString, code, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
return cmdString, 126, err
|
||||||
cmdFinish(1, cmdString, origInput)
|
|
||||||
} else {
|
} else {
|
||||||
cmdFinish(0, cmdString, origInput)
|
return cmdString, 0, nil
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if code, ok := interp.IsExitStatus(err); ok {
|
if code, ok := interp.IsExitStatus(err); ok {
|
||||||
cmdFinish(code, cmdString, origInput)
|
return cmdString, code, nil
|
||||||
} else {
|
} else {
|
||||||
cmdFinish(126, cmdString, origInput)
|
return cmdString, 126, err
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
cmdFinish(0, cmdString, origInput)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cmdString, 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run command in sh interpreter
|
// 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), "")
|
file, err := syntax.NewParser().Parse(strings.NewReader(cmd), "")
|
||||||
if err != nil {
|
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, " "))
|
_, argstring := splitInput(strings.Join(args, " "))
|
||||||
// i dont really like this but it works
|
// i dont really like this but it works
|
||||||
if aliases.All()[args[0]] != "" {
|
if aliases.All()[args[0]] != "" {
|
||||||
|
@ -101,74 +220,176 @@ func execCommand(cmd, old string) error {
|
||||||
|
|
||||||
// If alias was found, use command alias
|
// If alias was found, use command alias
|
||||||
argstring = aliases.Resolve(argstring)
|
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
|
// If command is defined in Lua then run it
|
||||||
luacmdArgs := l.NewTable()
|
luacmdArgs := rt.NewTable()
|
||||||
for _, str := range args[1:] {
|
for i, str := range args[1:] {
|
||||||
luacmdArgs.Append(lua.LString(str))
|
luacmdArgs.Set(rt.IntValue(int64(i + 1)), rt.StringValue(str))
|
||||||
}
|
}
|
||||||
|
|
||||||
if commands[args[0]] != nil {
|
if commands[args[0]] != nil {
|
||||||
err := l.CallByParam(lua.P{
|
luaexitcode, err := rt.Call1(l.MainThread(), rt.FunctionValue(commands[args[0]]), rt.TableValue(luacmdArgs))
|
||||||
Fn: commands[args[0]],
|
|
||||||
NRet: 1,
|
|
||||||
Protect: true,
|
|
||||||
}, luacmdArgs)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr,
|
fmt.Fprintln(os.Stderr, "Error in command:\n" + err.Error())
|
||||||
"Error in command:\n\n" + err.Error())
|
|
||||||
return interp.NewExitStatus(1)
|
return interp.NewExitStatus(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
luaexitcode := l.Get(-1)
|
|
||||||
var exitcode uint8
|
var exitcode uint8
|
||||||
|
|
||||||
l.Pop(1)
|
if code, ok := luaexitcode.TryInt(); ok {
|
||||||
|
|
||||||
if code, ok := luaexitcode.(lua.LNumber); luaexitcode != lua.LNil && ok {
|
|
||||||
exitcode = uint8(code)
|
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)
|
return interp.NewExitStatus(exitcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := lookpath(args[0])
|
err := lookpath(args[0])
|
||||||
if err == os.ErrPermission {
|
if err == errNotExec {
|
||||||
hooks.Em.Emit("command.no-perm", args[0])
|
hooks.Em.Emit("command.no-perm", args[0])
|
||||||
|
hooks.Em.Emit("command.not-executable", args[0])
|
||||||
return interp.NewExitStatus(126)
|
return interp.NewExitStatus(126)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
hooks.Em.Emit("command.not-found", args[0])
|
hooks.Em.Emit("command.not-found", args[0])
|
||||||
return interp.NewExitStatus(127)
|
return interp.NewExitStatus(127)
|
||||||
}
|
}
|
||||||
|
|
||||||
return interp.DefaultExecHandler(2 * time.Second)(ctx, args)
|
killTimeout := 2 * time.Second
|
||||||
}
|
// from here is basically copy-paste of the default exec handler from
|
||||||
runner, _ := interp.New(
|
// sh/interp but with our job handling
|
||||||
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
hc := interp.HandlerCtx(ctx)
|
||||||
interp.ExecHandler(exechandle),
|
path, err := interp.LookPathDir(hc.Dir, hc.Env, args[0])
|
||||||
)
|
if err != nil {
|
||||||
err = runner.Run(context.TODO(), file)
|
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 { // custom lookpath function so we know if a command is found *and* is executable
|
||||||
func lookpath(file string) error {
|
var skip []string
|
||||||
skip := []string{"./", "/", "../", "~/"}
|
if runtime.GOOS == "windows" {
|
||||||
|
skip = []string{"./", "../", "~/", "C:"}
|
||||||
|
} else {
|
||||||
|
skip = []string{"./", "/", "../", "~/"}
|
||||||
|
}
|
||||||
for _, s := range skip {
|
for _, s := range skip {
|
||||||
if strings.HasPrefix(file, s) {
|
if strings.HasPrefix(file, s) {
|
||||||
err := findExecutable(file)
|
return findExecutable(file, false, false)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
for _, dir := range filepath.SplitList(os.Getenv("PATH")) {
|
||||||
path := filepath.Join(dir, file)
|
path := filepath.Join(dir, file)
|
||||||
err := findExecutable(path)
|
err := findExecutable(path, true, false)
|
||||||
if err == os.ErrPermission {
|
if err == errNotExec {
|
||||||
return err
|
return err
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -178,17 +399,6 @@ func lookpath(file string) error {
|
||||||
return os.ErrNotExist
|
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) {
|
func splitInput(input string) ([]string, string) {
|
||||||
// end my suffering
|
// end my suffering
|
||||||
// TODO: refactor this garbage
|
// TODO: refactor this garbage
|
||||||
|
@ -242,11 +452,14 @@ func splitInput(input string) ([]string, string) {
|
||||||
return cmdArgs, cmdstr.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 input has space at the beginning, dont put in history
|
||||||
if interactive && !strings.HasPrefix(oldInput, " ") {
|
if interactive && !private {
|
||||||
handleHistory(strings.TrimSpace(oldInput))
|
handleHistory(cmdstr)
|
||||||
}
|
}
|
||||||
util.SetField(l, hshMod, "exitCode", lua.LNumber(code), "Exit code of last exected command")
|
util.SetField(l, hshMod, "exitCode", rt.IntValue(int64(code)), "Exit code of last exected command")
|
||||||
hooks.Em.Emit("command.exit", code, cmdstr)
|
// 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
|
module hilbish
|
||||||
|
|
||||||
go 1.16
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86
|
||||||
github.com/blackfireio/osinfo v1.0.3
|
github.com/blackfireio/osinfo v1.0.3
|
||||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9
|
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9
|
||||||
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036
|
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036
|
||||||
github.com/pborman/getopt v1.1.0
|
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
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||||
layeh.com/gopher-luar v1.0.10
|
|
||||||
mvdan.cc/sh/v3 v3.4.3
|
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 mvdan.cc/sh/v3 => github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e
|
||||||
|
|
||||||
replace github.com/maxlandon/readline => ./readline
|
replace github.com/maxlandon/readline => ./readline
|
||||||
|
|
||||||
replace layeh.com/gopher-luar => github.com/layeh/gopher-luar v1.0.10
|
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/golua v0.0.0-20220419183026-6d22d6fec5ac h1:dtXrgjch8PQyf7C90anZUquB5U3dr8AcMGJofeuirrI=
|
||||||
github.com/Rosettea/readline-1 v0.0.0-20220302012429-9ce5d23760f7/go.mod h1:QiUAvbhg8PzCA4hlafCUl0bKD/0VmcocM4AjqtszAJs=
|
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
|
||||||
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/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 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/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 h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
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 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c=
|
||||||
github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
|
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 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
|
||||||
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
|
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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
|
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
|
||||||
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
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 h1:MFq000a1ByoumoJWlytqg0qon0KlBeUfPsDjY0hK0bo=
|
||||||
github.com/evilsocket/islazy v1.10.6/go.mod h1:OrwQGYg3DuZvXUfmH+KIZDjwTCbrjy48T24TUpGqVVw=
|
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/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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
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.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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/layeh/gopher-luar v1.0.10 h1:8NIv4MX1Arz96kK4buGK1D87DyDxKZyq6KKvJ2diHp0=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/layeh/gopher-luar v1.0.10/go.mod h1:TPnIVCZ2RJBndm7ohXyaqfhzjlZ+OA2SZR/YwL8tECk=
|
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 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=
|
||||||
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=
|
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 h1:eJ3aFZroQqq0bWmraivjQNt6Dmm5M0h2JcDW38/Azb0=
|
||||||
github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk=
|
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/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 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
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.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 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
|
||||||
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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-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-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-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-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220224120231-95c6836cb0e7/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 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
|
||||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-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 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
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/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
|
mvdan.cc/editorconfig v0.2.0/go.mod h1:lvnnD3BNdBYkhq+B4uBuFFKatfp02eB6HixDvEz91C0=
|
||||||
|
|
|
@ -4,13 +4,14 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
"github.com/chuckpreslar/emission"
|
"github.com/chuckpreslar/emission"
|
||||||
"github.com/yuin/gopher-lua"
|
|
||||||
"layeh.com/gopher-luar"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bait struct{
|
type Bait struct{
|
||||||
Em *emission.Emitter
|
Em *emission.Emitter
|
||||||
|
Loader packagelib.Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() Bait {
|
func New() Bait {
|
||||||
|
@ -19,15 +20,27 @@ func New() Bait {
|
||||||
emitter.Off(hookname, hookfunc)
|
emitter.Off(hookname, hookfunc)
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
})
|
})
|
||||||
return Bait{
|
b := Bait{
|
||||||
Em: emitter,
|
Em: emitter,
|
||||||
}
|
}
|
||||||
|
b.Loader = packagelib.Loader{
|
||||||
|
Load: b.loaderFunc,
|
||||||
|
Name: "bait",
|
||||||
|
}
|
||||||
|
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bait) Loader(L *lua.LState) int {
|
func (b *Bait) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
mod := L.SetFuncs(L.NewTable(), map[string]lua.LGFunction{})
|
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?
|
`Bait is the event emitter for Hilbish. Why name it bait?
|
||||||
Because it throws hooks that you can catch (emits events
|
Because it throws hooks that you can catch (emits events
|
||||||
that you can listen to) and because why not, fun naming
|
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
|
like when you've changed directory, a command has
|
||||||
failed, etc. To find all available hooks, see doc hooks.`)
|
failed, etc. To find all available hooks, see doc hooks.`)
|
||||||
|
|
||||||
L.SetField(mod, "throw", luar.New(L, b.bthrow))
|
return rt.TableValue(mod), nil
|
||||||
L.SetField(mod, "catch", luar.New(L, b.bcatch))
|
}
|
||||||
L.SetField(mod, "catchOnce", luar.New(L, b.bcatchOnce))
|
|
||||||
|
|
||||||
L.Push(mod)
|
func handleHook(t *rt.Thread, c *rt.GoCont, name string, catcher *rt.Closure, args ...interface{}) {
|
||||||
|
funcVal := rt.FunctionValue(catcher)
|
||||||
return 1
|
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)
|
// throw(name, ...args)
|
||||||
// Throws a hook with `name` with the provided `args`
|
// Throws a hook with `name` with the provided `args`
|
||||||
// --- @param name string
|
// --- @param name string
|
||||||
// --- @vararg any
|
// --- @vararg any
|
||||||
func (b *Bait) bthrow(name string, args ...interface{}) {
|
func (b *Bait) bthrow(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
b.Em.Emit(name, args...)
|
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)
|
// catch(name, cb)
|
||||||
// Catches a hook with `name`. Runs the `cb` when it is thrown
|
// Catches a hook with `name`. Runs the `cb` when it is thrown
|
||||||
// --- @param name string
|
// --- @param name string
|
||||||
// --- @param cb function
|
// --- @param cb function
|
||||||
func (b *Bait) bcatch(name string, catcher func(...interface{})) {
|
func (b *Bait) bcatch(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
b.Em.On(name, catcher)
|
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)
|
// catchOnce(name, cb)
|
||||||
// Same as catch, but only runs the `cb` once and then removes the hook
|
// Same as catch, but only runs the `cb` once and then removes the hook
|
||||||
// --- @param name string
|
// --- @param name string
|
||||||
// --- @param cb function
|
// --- @param cb function
|
||||||
func (b *Bait) bcatchOnce(name string, catcher func(...interface{})) {
|
func (b *Bait) bcatchOnce(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
b.Em.Once(name, catcher)
|
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 (
|
import (
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
"github.com/chuckpreslar/emission"
|
"github.com/chuckpreslar/emission"
|
||||||
"github.com/yuin/gopher-lua"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Commander struct{
|
type Commander struct{
|
||||||
Events *emission.Emitter
|
Events *emission.Emitter
|
||||||
|
Loader packagelib.Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() Commander {
|
func New() Commander {
|
||||||
return Commander{
|
c := Commander{
|
||||||
Events: emission.NewEmitter(),
|
Events: emission.NewEmitter(),
|
||||||
}
|
}
|
||||||
|
c.Loader = packagelib.Loader{
|
||||||
|
Load: c.loaderFunc,
|
||||||
|
Name: "commander",
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commander) Loader(L *lua.LState) int {
|
func (c *Commander) loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
exports := map[string]lua.LGFunction{
|
exports := map[string]util.LuaExport{
|
||||||
"register": c.cregister,
|
"register": util.LuaExport{c.cregister, 2, false},
|
||||||
"deregister": c.cderegister,
|
"deregister": util.LuaExport{c.cderegister, 1, false},
|
||||||
}
|
}
|
||||||
mod := L.SetFuncs(L.NewTable(), exports)
|
mod := rt.NewTable()
|
||||||
util.Document(L, mod, "Commander is Hilbish's custom command library, a way to write commands in Lua.")
|
util.SetExports(rtm, mod, exports)
|
||||||
L.Push(mod)
|
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(name, cb)
|
||||||
// Register a command with `name` that runs `cb` when ran
|
// Register a command with `name` that runs `cb` when ran
|
||||||
// --- @param name string
|
// --- @param name string
|
||||||
// --- @param cb function
|
// --- @param cb function
|
||||||
func (c *Commander) cregister(L *lua.LState) int {
|
func (c *Commander) cregister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
|
||||||
cmdName := L.CheckString(1)
|
cmdName, cmd, err := util.HandleStrCallback(t, ct)
|
||||||
cmd := L.CheckFunction(2)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
c.Events.Emit("commandRegister", cmdName, cmd)
|
c.Events.Emit("commandRegister", cmdName, cmd)
|
||||||
|
|
||||||
return 0
|
return ct.Next(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// deregister(name)
|
// deregister(name)
|
||||||
// Deregisters any command registered with `name`
|
// Deregisters any command registered with `name`
|
||||||
// --- @param name string
|
// --- @param name string
|
||||||
func (c *Commander) cderegister(L *lua.LState) int {
|
func (c *Commander) cderegister(t *rt.Thread, ct *rt.GoCont) (rt.Cont, error) {
|
||||||
cmdName := L.CheckString(1)
|
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)
|
c.Events.Emit("commandDeregister", cmdName)
|
||||||
|
|
||||||
return 0
|
return ct.Next(), err
|
||||||
}
|
}
|
||||||
|
|
122
golibs/fs/fs.go
122
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
|
package fs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -8,51 +6,70 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"hilbish/util"
|
"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 {
|
var Loader = packagelib.Loader{
|
||||||
mod := L.SetFuncs(L.NewTable(), exports)
|
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
|
filesystem functions and other things, and acts an
|
||||||
addition to the Lua standard library's I/O and fs functions.`)
|
addition to the Lua standard library's I/O and fs functions.`)
|
||||||
|
|
||||||
L.Push(mod)
|
return rt.TableValue(mod), nil
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var exports = map[string]lua.LGFunction{
|
|
||||||
"cd": fcd,
|
|
||||||
"mkdir": fmkdir,
|
|
||||||
"stat": fstat,
|
|
||||||
"readdir": freaddir,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// cd(dir)
|
// cd(dir)
|
||||||
// Changes directory to `dir`
|
// Changes directory to `dir`
|
||||||
// --- @param dir string
|
// --- @param dir string
|
||||||
func fcd(L *lua.LState) int {
|
func fcd(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
path := L.CheckString(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
err := os.Chdir(strings.TrimSpace(path))
|
}
|
||||||
|
path, err := c.StringArg(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
e := err.(*os.PathError).Err.Error()
|
return nil, err
|
||||||
L.RaiseError(e + ": " + path)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
err = os.Chdir(strings.TrimSpace(path))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Next(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// mkdir(name, recursive)
|
// mkdir(name, recursive)
|
||||||
// Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
|
// Makes a directory called `name`. If `recursive` is true, it will create its parent directories.
|
||||||
// --- @param name string
|
// --- @param name string
|
||||||
// --- @param recursive boolean
|
// --- @param recursive boolean
|
||||||
func fmkdir(L *lua.LState) int {
|
func fmkdir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
dirname := L.CheckString(1)
|
if err := c.CheckNArgs(2); err != nil {
|
||||||
recursive := L.ToBool(2)
|
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)
|
path := strings.TrimSpace(dirname)
|
||||||
var err error
|
|
||||||
|
|
||||||
if recursive {
|
if recursive {
|
||||||
err = os.MkdirAll(path, 0744)
|
err = os.MkdirAll(path, 0744)
|
||||||
|
@ -60,51 +77,58 @@ func fmkdir(L *lua.LState) int {
|
||||||
err = os.Mkdir(path, 0744)
|
err = os.Mkdir(path, 0744)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
L.RaiseError(err.Error() + ": " + path)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return c.Next(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// stat(path)
|
// stat(path)
|
||||||
// Returns info about `path`
|
// Returns info about `path`
|
||||||
// --- @param path string
|
// --- @param path string
|
||||||
func fstat(L *lua.LState) int {
|
func fstat(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
path := L.CheckString(1)
|
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)
|
pathinfo, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
L.RaiseError(err.Error() + ": " + path)
|
return nil, err
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
statTbl := L.NewTable()
|
statTbl := rt.NewTable()
|
||||||
L.SetField(statTbl, "name", lua.LString(pathinfo.Name()))
|
statTbl.Set(rt.StringValue("name"), rt.StringValue(pathinfo.Name()))
|
||||||
L.SetField(statTbl, "size", lua.LNumber(pathinfo.Size()))
|
statTbl.Set(rt.StringValue("size"), rt.IntValue(pathinfo.Size()))
|
||||||
L.SetField(statTbl, "mode", lua.LString("0" + strconv.FormatInt(int64(pathinfo.Mode().Perm()), 8)))
|
statTbl.Set(rt.StringValue("mode"), rt.StringValue("0" + strconv.FormatInt(int64(pathinfo.Mode().Perm()), 8)))
|
||||||
L.SetField(statTbl, "isDir", lua.LBool(pathinfo.IsDir()))
|
statTbl.Set(rt.StringValue("isDir"), rt.BoolValue(pathinfo.IsDir()))
|
||||||
L.Push(statTbl)
|
|
||||||
|
|
||||||
return 1
|
return c.PushingNext1(t.Runtime, rt.TableValue(statTbl)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// readdir(dir)
|
// readdir(dir)
|
||||||
// Returns a table of files in `dir`
|
// Returns a table of files in `dir`
|
||||||
// --- @param dir string
|
// --- @param dir string
|
||||||
// --- @return table
|
// --- @return table
|
||||||
func freaddir(L *lua.LState) int {
|
func freaddir(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
dir := L.CheckString(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
names := L.NewTable()
|
return nil, err
|
||||||
|
}
|
||||||
|
dir, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
names := rt.NewTable()
|
||||||
|
|
||||||
dirEntries, err := os.ReadDir(dir)
|
dirEntries, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
L.RaiseError(err.Error() + ": " + dir)
|
return nil, err
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
for _, entry := range dirEntries {
|
for i, entry := range dirEntries {
|
||||||
names.Append(lua.LString(entry.Name()))
|
names.Set(rt.IntValue(int64(i + 1)), rt.StringValue(entry.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
L.Push(names)
|
return c.PushingNext1(t.Runtime, rt.TableValue(names)), nil
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,76 +5,78 @@ import (
|
||||||
|
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"github.com/arnodel/golua/lib/packagelib"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
"github.com/yuin/gopher-lua"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var termState *term.State
|
var termState *term.State
|
||||||
|
var Loader = packagelib.Loader{
|
||||||
func Loader(L *lua.LState) int {
|
Load: loaderFunc,
|
||||||
mod := L.SetFuncs(L.NewTable(), exports)
|
Name: "terminal",
|
||||||
util.Document(L, mod, "The terminal library is a simple and lower level library for certain terminal interactions.")
|
|
||||||
|
|
||||||
L.Push(mod)
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var exports = map[string]lua.LGFunction{
|
func loaderFunc(rtm *rt.Runtime) (rt.Value, func()) {
|
||||||
"setRaw": termraw,
|
exports := map[string]util.LuaExport{
|
||||||
"restoreState": termrestoreState,
|
"setRaw": util.LuaExport{termsetRaw, 0, false},
|
||||||
"size": termsize,
|
"restoreState": util.LuaExport{termrestoreState, 0, false},
|
||||||
"saveState": termsaveState,
|
"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()
|
// size()
|
||||||
// Gets the dimensions of the terminal. Returns a table with `width` and `height`
|
// 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
|
// 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()))
|
w, h, err := term.GetSize(int(os.Stdin.Fd()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
L.RaiseError(err.Error())
|
return nil, err
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
dimensions := L.NewTable()
|
|
||||||
L.SetField(dimensions, "width", lua.LNumber(w))
|
|
||||||
L.SetField(dimensions, "height", lua.LNumber(h))
|
|
||||||
|
|
||||||
L.Push(dimensions)
|
dimensions := rt.NewTable()
|
||||||
return 1
|
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()
|
// saveState()
|
||||||
// Saves the current state of the terminal
|
// 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()))
|
state, err := term.GetState(int(os.Stdin.Fd()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
L.RaiseError(err.Error())
|
return nil, err
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
termState = state
|
termState = state
|
||||||
return 0
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// restoreState()
|
// restoreState()
|
||||||
// Restores the last saved state of the terminal
|
// 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)
|
err := term.Restore(int(os.Stdin.Fd()), termState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
L.RaiseError(err.Error())
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setRaw()
|
// setRaw()
|
||||||
// Puts the terminal in raw mode
|
// 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()))
|
_, err := term.MakeRaw(int(os.Stdin.Fd()))
|
||||||
if err != nil {
|
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{} {
|
func (h *fileHistory) Dump() interface{} {
|
||||||
return h.items
|
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)
|
ansikit.print = function(text)
|
||||||
io.write(ansikit.format(text))
|
io.write(ansikit.format(text))
|
||||||
|
io.flush()
|
||||||
return ansikit
|
return ansikit
|
||||||
end
|
end
|
||||||
|
|
||||||
ansikit.printCode = function(code, terminate)
|
ansikit.printCode = function(code, terminate)
|
||||||
io.write(ansikit.getCode(code, terminate))
|
io.write(ansikit.getCode(code, terminate))
|
||||||
|
io.flush()
|
||||||
return ansikit
|
return ansikit
|
||||||
end
|
end
|
||||||
|
|
||||||
ansikit.printCSI = function(code, endc)
|
ansikit.printCSI = function(code, endc)
|
||||||
io.write(ansikit.getCSI(code, endc))
|
io.write(ansikit.getCSI(code, endc))
|
||||||
|
io.flush()
|
||||||
return ansikit
|
return ansikit
|
||||||
end
|
end
|
||||||
|
|
||||||
ansikit.println = function(text)
|
ansikit.println = function(text)
|
||||||
print(ansikit.print(text))
|
io.write(ansikit.format(text) .. "\n")
|
||||||
|
io.flush()
|
||||||
return ansikit
|
return ansikit
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 5a59d0f4543eb982593750c52f7393e2fd2d15f9
|
Subproject commit b362397a83e4516415c809c7d690b52e79a95f6e
|
47
lua.go
47
lua.go
|
@ -4,40 +4,42 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"hilbish/util"
|
||||||
"hilbish/golibs/bait"
|
"hilbish/golibs/bait"
|
||||||
"hilbish/golibs/commander"
|
"hilbish/golibs/commander"
|
||||||
"hilbish/golibs/fs"
|
"hilbish/golibs/fs"
|
||||||
"hilbish/golibs/terminal"
|
"hilbish/golibs/terminal"
|
||||||
|
|
||||||
"github.com/yuin/gopher-lua"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
|
"github.com/arnodel/golua/lib"
|
||||||
)
|
)
|
||||||
|
|
||||||
var minimalconf = `hilbish.prompt '& '`
|
var minimalconf = `hilbish.prompt '& '`
|
||||||
|
|
||||||
func luaInit() {
|
func luaInit() {
|
||||||
l = lua.NewState()
|
l = rt.New(os.Stdout)
|
||||||
l.OpenLibs()
|
lib.LoadAll(l)
|
||||||
|
|
||||||
|
lib.LoadLibs(l, hilbishLoader)
|
||||||
// yes this is stupid, i know
|
// yes this is stupid, i know
|
||||||
l.PreloadModule("hilbish", hilbishLoader)
|
util.DoString(l, "hilbish = require 'hilbish'")
|
||||||
l.DoString("hilbish = require 'hilbish'")
|
|
||||||
|
|
||||||
// Add fs and terminal module module to Lua
|
// Add fs and terminal module module to Lua
|
||||||
l.PreloadModule("fs", fs.Loader)
|
lib.LoadLibs(l, fs.Loader)
|
||||||
l.PreloadModule("terminal", terminal.Loader)
|
lib.LoadLibs(l, terminal.Loader)
|
||||||
|
|
||||||
cmds := commander.New()
|
cmds := commander.New()
|
||||||
// When a command from Lua is added, register it for use
|
// 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
|
commands[cmdName] = cmd
|
||||||
})
|
})
|
||||||
cmds.Events.On("commandDeregister", func(cmdName string) {
|
cmds.Events.On("commandDeregister", func(cmdName string) {
|
||||||
delete(commands, cmdName)
|
delete(commands, cmdName)
|
||||||
})
|
})
|
||||||
l.PreloadModule("commander", cmds.Loader)
|
lib.LoadLibs(l, cmds.Loader)
|
||||||
|
|
||||||
hooks = bait.New()
|
hooks = bait.New()
|
||||||
l.PreloadModule("bait", hooks.Loader)
|
lib.LoadLibs(l, hooks.Loader)
|
||||||
|
|
||||||
// Add Ctrl-C handler
|
// Add Ctrl-C handler
|
||||||
hooks.Em.On("signal.sigint", func() {
|
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
|
// Add more paths that Lua can require from
|
||||||
l.DoString("package.path = package.path .. " + requirePaths)
|
err := util.DoString(l, "package.path = package.path .. " + requirePaths)
|
||||||
|
|
||||||
err := l.DoFile("prelude/init.lua")
|
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr,
|
fmt.Fprintln(os.Stderr, "Missing preload file, builtins may be missing.")
|
||||||
"Missing preload file, builtins may be missing.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runConfig(confpath string) {
|
func runConfig(confpath string) {
|
||||||
if !interactive {
|
if !interactive {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := l.DoFile(confpath)
|
err := util.DoFile(l, confpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err,
|
fmt.Fprintln(os.Stderr, err, "\nAn error has occured while loading your config! Falling back to minimal default config.")
|
||||||
"\nAn error has occured while loading your config! Falling back to minimal default config.")
|
util.DoString(l, minimalconf)
|
||||||
|
|
||||||
l.DoString(minimalconf)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
62
main.go
62
main.go
|
@ -10,20 +10,21 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"hilbish/util"
|
||||||
"hilbish/golibs/bait"
|
"hilbish/golibs/bait"
|
||||||
|
|
||||||
|
rt "github.com/arnodel/golua/runtime"
|
||||||
"github.com/pborman/getopt"
|
"github.com/pborman/getopt"
|
||||||
"github.com/yuin/gopher-lua"
|
|
||||||
"github.com/maxlandon/readline"
|
"github.com/maxlandon/readline"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
l *lua.LState
|
l *rt.Runtime
|
||||||
lr *lineReader
|
lr *lineReader
|
||||||
|
|
||||||
commands = map[string]*lua.LFunction{}
|
commands = map[string]*rt.Closure{}
|
||||||
luaCompletions = map[string]*lua.LFunction{}
|
luaCompletions = map[string]*rt.Closure{}
|
||||||
|
|
||||||
confDir string
|
confDir string
|
||||||
userDataDir string
|
userDataDir string
|
||||||
|
@ -43,7 +44,7 @@ func main() {
|
||||||
|
|
||||||
// i honestly dont know what directories to use for this
|
// i honestly dont know what directories to use for this
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "linux":
|
case "linux", "darwin":
|
||||||
userDataDir = getenv("XDG_DATA_HOME", curuser.HomeDir + "/.local/share")
|
userDataDir = getenv("XDG_DATA_HOME", curuser.HomeDir + "/.local/share")
|
||||||
default:
|
default:
|
||||||
// this is fine on windows, dont know about others
|
// this is fine on windows, dont know about others
|
||||||
|
@ -55,7 +56,7 @@ func main() {
|
||||||
defaultConfDir = filepath.Join(confDir, "hilbish")
|
defaultConfDir = filepath.Join(confDir, "hilbish")
|
||||||
} else {
|
} else {
|
||||||
// else do ~ substitution
|
// else do ~ substitution
|
||||||
defaultConfDir = expandHome(defaultHistDir)
|
defaultConfDir = filepath.Join(expandHome(defaultConfDir), "hilbish")
|
||||||
}
|
}
|
||||||
defaultConfPath = filepath.Join(defaultConfDir, "init.lua")
|
defaultConfPath = filepath.Join(defaultConfDir, "init.lua")
|
||||||
if defaultHistDir == "" {
|
if defaultHistDir == "" {
|
||||||
|
@ -142,27 +143,28 @@ func main() {
|
||||||
scanner := bufio.NewScanner(bufio.NewReader(os.Stdin))
|
scanner := bufio.NewScanner(bufio.NewReader(os.Stdin))
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
text := scanner.Text()
|
text := scanner.Text()
|
||||||
runInput(text, text)
|
runInput(text, true)
|
||||||
}
|
}
|
||||||
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *cmdflag != "" {
|
if *cmdflag != "" {
|
||||||
runInput(*cmdflag, *cmdflag)
|
runInput(*cmdflag, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if getopt.NArgs() > 0 {
|
if getopt.NArgs() > 0 {
|
||||||
luaArgs := l.NewTable()
|
luaArgs := rt.NewTable()
|
||||||
for _, arg := range getopt.Args() {
|
for i, arg := range getopt.Args() {
|
||||||
luaArgs.Append(lua.LString(arg))
|
luaArgs.Set(rt.IntValue(int64(i)), rt.StringValue(arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
l.SetGlobal("args", luaArgs)
|
l.GlobalEnv().Set(rt.StringValue("args"), rt.TableValue(luaArgs))
|
||||||
err := l.DoFile(getopt.Arg(0))
|
err := util.DoFile(l, getopt.Arg(0))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
initialized = true
|
initialized = true
|
||||||
|
@ -185,7 +187,10 @@ input:
|
||||||
fmt.Println("^C")
|
fmt.Println("^C")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
oldInput := input
|
var priv bool
|
||||||
|
if strings.HasPrefix(input, " ") {
|
||||||
|
priv = true
|
||||||
|
}
|
||||||
|
|
||||||
input = strings.TrimSpace(input)
|
input = strings.TrimSpace(input)
|
||||||
if len(input) == 0 {
|
if len(input) == 0 {
|
||||||
|
@ -198,6 +203,8 @@ input:
|
||||||
for {
|
for {
|
||||||
input, err = continuePrompt(input)
|
input, err = continuePrompt(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
running = true
|
||||||
|
lr.SetPrompt(fmtPrompt(prompt))
|
||||||
goto input // continue inside nested loop
|
goto input // continue inside nested loop
|
||||||
}
|
}
|
||||||
if !strings.HasSuffix(input, "\\") {
|
if !strings.HasSuffix(input, "\\") {
|
||||||
|
@ -206,7 +213,7 @@ input:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runInput(input, oldInput)
|
runInput(input, priv)
|
||||||
|
|
||||||
termwidth, _, err := term.GetSize(0)
|
termwidth, _, err := term.GetSize(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -268,8 +275,7 @@ func handleHistory(cmd string) {
|
||||||
|
|
||||||
func expandHome(path string) string {
|
func expandHome(path string) string {
|
||||||
homedir := curuser.HomeDir
|
homedir := curuser.HomeDir
|
||||||
|
return strings.Replace(path, "~", homedir, 1)
|
||||||
return strings.Replace(defaultHistDir, "~", homedir, 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeDupes(slice []string) []string {
|
func removeDupes(slice []string) []string {
|
||||||
|
@ -284,3 +290,21 @@ func removeDupes(slice []string) []string {
|
||||||
|
|
||||||
return newSlice
|
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 oldDir = hilbish.cwd()
|
||||||
|
|
||||||
local shlvl = tonumber(os.getenv 'SHLVL')
|
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
|
-- Builtins
|
||||||
local recentDirs = {}
|
local recentDirs = {}
|
||||||
|
@ -168,6 +168,9 @@ hilbish.userDir.config .. '/hilbish/init.lua' ..
|
||||||
and also change all global functions (prompt, alias) to be
|
and also change all global functions (prompt, alias) to be
|
||||||
in the hilbish module (hilbish.prompt, hilbish.alias as examples).
|
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
|
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
|
at https://github.com/Rosettea/Hilbish/releases/tag/v1.0.0
|
||||||
to find more breaking changes.
|
to find more breaking changes.
|
||||||
|
@ -214,14 +217,6 @@ do
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
commander.register('cdr', function(args)
|
commander.register('cdr', function(args)
|
||||||
|
@ -263,7 +258,7 @@ bait.catch('command.not-found', function(cmd)
|
||||||
print(string.format('hilbish: %s not found', cmd))
|
print(string.format('hilbish: %s not found', cmd))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
bait.catch('command.no-perm', function(cmd)
|
bait.catch('command.not-executable', function(cmd)
|
||||||
print(string.format('hilbish: %s: no permission', cmd))
|
print(string.format('hilbish: %s: not executable', cmd))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
@ -34,32 +34,39 @@ const (
|
||||||
charCtrlHat // ^^
|
charCtrlHat // ^^
|
||||||
charCtrlUnderscore // ^_
|
charCtrlUnderscore // ^_
|
||||||
charBackspace2 = 127 // ASCII 1963
|
charBackspace2 = 127 // ASCII 1963
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Escape sequences
|
// Escape sequences
|
||||||
var (
|
var (
|
||||||
seqUp = string([]byte{27, 91, 65})
|
seqUp = string([]byte{27, 91, 65})
|
||||||
seqDown = string([]byte{27, 91, 66})
|
seqDown = string([]byte{27, 91, 66})
|
||||||
seqForwards = string([]byte{27, 91, 67})
|
seqForwards = string([]byte{27, 91, 67})
|
||||||
seqBackwards = string([]byte{27, 91, 68})
|
seqBackwards = string([]byte{27, 91, 68})
|
||||||
seqHome = string([]byte{27, 91, 72})
|
seqHome = string([]byte{27, 91, 72})
|
||||||
seqHomeSc = string([]byte{27, 91, 49, 126})
|
seqHomeSc = string([]byte{27, 91, 49, 126})
|
||||||
seqEnd = string([]byte{27, 91, 70})
|
seqEnd = string([]byte{27, 91, 70})
|
||||||
seqEndSc = string([]byte{27, 91, 52, 126})
|
seqEndSc = string([]byte{27, 91, 52, 126})
|
||||||
seqDelete = string([]byte{27, 91, 51, 126})
|
seqDelete = string([]byte{27, 91, 51, 126})
|
||||||
seqShiftTab = string([]byte{27, 91, 90})
|
seqDelete2 = string([]byte{27, 91, 80})
|
||||||
seqAltQuote = string([]byte{27, 34}) // Added for showing registers ^["
|
seqCtrlDelete = string([]byte{27, 91, 51, 59, 53, 126})
|
||||||
seqAltR = string([]byte{27, 114}) // Used for alternative history
|
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 (
|
const (
|
||||||
seqPosSave = "\x1b[s"
|
seqPosSave = "\x1b[s"
|
||||||
seqPosRestore = "\x1b[u"
|
seqPosRestore = "\x1b[u"
|
||||||
|
|
||||||
seqClearLineAfer = "\x1b[0k"
|
seqClearLineAfer = "\x1b[0K"
|
||||||
seqClearLineBefore = "\x1b[1k"
|
seqClearLineBefore = "\x1b[1K"
|
||||||
seqClearLine = "\x1b[2k"
|
seqClearLine = "\x1b[2K"
|
||||||
seqClearScreenBelow = "\x1b[0J"
|
seqClearScreenBelow = "\x1b[0J"
|
||||||
seqClearScreen = "\x1b[2J" // Clears screen fully
|
seqClearScreen = "\x1b[2J" // Clears screen fully
|
||||||
seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left
|
seqCursorTopLeft = "\x1b[H" // Clears screen and places cursor on top-left
|
||||||
|
@ -78,6 +85,7 @@ const (
|
||||||
seqBold = "\x1b[1m"
|
seqBold = "\x1b[1m"
|
||||||
seqUnderscore = "\x1b[4m"
|
seqUnderscore = "\x1b[4m"
|
||||||
seqBlink = "\x1b[5m"
|
seqBlink = "\x1b[5m"
|
||||||
|
seqInvert = "\x1b[7m"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Text colours
|
// Text colours
|
||||||
|
|
|
@ -121,7 +121,7 @@ func (g *CompletionGroup) writeGrid(rl *Instance) (comp string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x == g.tcPosX && y == g.tcPosY) && (g.isCurrent) {
|
if (x == g.tcPosX && y == g.tcPosY) && (g.isCurrent) {
|
||||||
comp += seqCtermFg255 + seqFgBlackBright
|
comp += seqInvert
|
||||||
}
|
}
|
||||||
|
|
||||||
comp += fmt.Sprintf("%-"+cellWidth+"s %s", g.Suggestions[i], seqReset)
|
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.
|
// function highlights the cell depending on current selector place.
|
||||||
highlight := func(y int, x int) string {
|
highlight := func(y int, x int) string {
|
||||||
if y == g.tcPosY && x == g.tcPosX && g.isCurrent {
|
if y == g.tcPosY && x == g.tcPosX && g.isCurrent {
|
||||||
return seqCtermFg255 + seqFgBlackBright
|
return seqInvert
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,7 +101,7 @@ func (g *CompletionGroup) writeMap(rl *Instance) (comp string) {
|
||||||
// Highlighting function
|
// Highlighting function
|
||||||
highlight := func(y int) string {
|
highlight := func(y int) string {
|
||||||
if y == g.tcPosY && g.isCurrent {
|
if y == g.tcPosY && g.isCurrent {
|
||||||
return seqCtermFg255 + seqFgBlackBright
|
return seqInvert
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,12 +100,12 @@ func moveCursorBackwards(i int) {
|
||||||
printf("\x1b[%dD", i)
|
printf("\x1b[%dD", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) backspace() {
|
func (rl *Instance) backspace(forward bool) {
|
||||||
if len(rl.line) == 0 || rl.pos == 0 {
|
if len(rl.line) == 0 || rl.pos == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.deleteBackspace()
|
rl.deleteBackspace(forward)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) moveCursorByAdjust(adjust int) {
|
func (rl *Instance) moveCursorByAdjust(adjust int) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ type EventReturn struct {
|
||||||
ForwardKey bool
|
ForwardKey bool
|
||||||
ClearHelpers bool
|
ClearHelpers bool
|
||||||
CloseReadline bool
|
CloseReadline bool
|
||||||
HintText []rune
|
InfoText []rune
|
||||||
NewLine []rune
|
NewLine []rune
|
||||||
NewPos int
|
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
|
// 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.
|
// them, so the hint will survive until the helpers (thus including the hint) will be updated/recomputed.
|
||||||
|
/*
|
||||||
func (rl *Instance) SetHintText(s string) {
|
func (rl *Instance) SetHintText(s string) {
|
||||||
rl.hintText = []rune(s)
|
rl.hintText = []rune(s)
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
func (rl *Instance) getHintText() {
|
func (rl *Instance) getHintText() {
|
||||||
|
|
||||||
|
@ -27,7 +29,7 @@ func (rl *Instance) getHintText() {
|
||||||
// writeHintText - only writes the hint text and computes its offsets.
|
// writeHintText - only writes the hint text and computes its offsets.
|
||||||
func (rl *Instance) writeHintText() {
|
func (rl *Instance) writeHintText() {
|
||||||
if len(rl.hintText) == 0 {
|
if len(rl.hintText) == 0 {
|
||||||
rl.hintY = 0
|
//rl.hintY = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,16 +43,16 @@ func (rl *Instance) writeHintText() {
|
||||||
|
|
||||||
wrapped, hintLen := WrapText(string(rl.hintText), width)
|
wrapped, hintLen := WrapText(string(rl.hintText), width)
|
||||||
offset += hintLen
|
offset += hintLen
|
||||||
rl.hintY = offset
|
// rl.hintY = offset
|
||||||
|
|
||||||
hintText := string(wrapped)
|
hintText := string(wrapped)
|
||||||
|
|
||||||
if len(hintText) > 0 {
|
if len(hintText) > 0 {
|
||||||
print("\r" + rl.HintFormatting + string(hintText) + seqReset)
|
print(rl.HintFormatting + string(hintText) + seqReset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) resetHintText() {
|
func (rl *Instance) resetHintText() {
|
||||||
rl.hintY = 0
|
//rl.hintY = 0
|
||||||
rl.hintText = []rune{}
|
rl.hintText = []rune{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,13 +183,13 @@ func (rl *Instance) completeHistory() (hist []*CompletionGroup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
history = rl.altHistory
|
history = rl.altHistory
|
||||||
rl.histHint = []rune(rl.altHistName + ": ")
|
rl.histInfo = []rune(rl.altHistName + ": ")
|
||||||
} else {
|
} else {
|
||||||
if rl.mainHistory == nil {
|
if rl.mainHistory == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
history = rl.mainHistory
|
history = rl.mainHistory
|
||||||
rl.histHint = []rune(rl.mainHistName + ": ")
|
rl.histInfo = []rune(rl.mainHistName + ": ")
|
||||||
}
|
}
|
||||||
|
|
||||||
hist[0].init(rl)
|
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.
|
Multiline bool // If set to true, the shell will have a two-line prompt.
|
||||||
MultilinePrompt string // If multiline is true, this is the content of the 2nd line.
|
MultilinePrompt string // If multiline is true, this is the content of the 2nd line.
|
||||||
|
|
||||||
mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt
|
mainPrompt string // If multiline true, the full prompt string / If false, the 1st line of the prompt
|
||||||
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
|
rightPrompt string
|
||||||
defaultPrompt []rune
|
rightPromptLen int
|
||||||
promptLen int
|
realPrompt []rune // The prompt that is actually on the same line as the beginning of the input line.
|
||||||
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
defaultPrompt []rune
|
||||||
|
promptLen int
|
||||||
|
stillOnRefresh bool // True if some logs have printed asynchronously since last loop. Check refresh prompt funcs
|
||||||
|
|
||||||
//
|
//
|
||||||
// Input Line ---------------------------------------------------------------------------------
|
// Input Line ---------------------------------------------------------------------------------
|
||||||
|
@ -110,7 +112,7 @@ type Instance struct {
|
||||||
searchMode FindMode // Used for varying hints, and underlying functions called
|
searchMode FindMode // Used for varying hints, and underlying functions called
|
||||||
regexSearch *regexp.Regexp // Holds the current search regex match
|
regexSearch *regexp.Regexp // Holds the current search regex match
|
||||||
mainHist bool // Which history stdin do we want
|
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 -----------------------------------------------------------------------------------
|
// History -----------------------------------------------------------------------------------
|
||||||
|
@ -134,19 +136,33 @@ type Instance struct {
|
||||||
histNavIdx int // Used for quick history navigation.
|
histNavIdx int // Used for quick history navigation.
|
||||||
|
|
||||||
//
|
//
|
||||||
// Hints -------------------------------------------------------------------------------------
|
// Info -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// HintText is a helper function which displays hint text the prompt.
|
// InfoText is a helper function which displays infio text below the prompt.
|
||||||
// HintText takes the line input from the promt and the cursor position.
|
// 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.
|
// It returns the hint text to display.
|
||||||
HintText func([]rune, int) []rune
|
HintText func([]rune, int) []rune
|
||||||
|
|
||||||
// HintColor any ANSI escape codes you wish to use for hint formatting. By
|
// HintFormatting is just a string to use as the formatting for the hint. By default
|
||||||
// default this will just be blue.
|
// this will be a grey color.
|
||||||
HintFormatting string
|
HintFormatting string
|
||||||
|
|
||||||
hintText []rune // The actual hint text
|
hintText []rune
|
||||||
hintY int // Offset to hints, if it spans multiple lines
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Vim Operatng Parameters -------------------------------------------------------------------
|
// Vim Operatng Parameters -------------------------------------------------------------------
|
||||||
|
@ -205,7 +221,8 @@ func NewInstance() *Instance {
|
||||||
rl.HistoryAutoWrite = true
|
rl.HistoryAutoWrite = true
|
||||||
|
|
||||||
// Others
|
// Others
|
||||||
rl.HintFormatting = seqFgBlue
|
rl.InfoFormatting = seqFgBlue
|
||||||
|
rl.HintFormatting = "\x1b[2m"
|
||||||
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
|
||||||
rl.TempDirectory = os.TempDir()
|
rl.TempDirectory = os.TempDir()
|
||||||
|
|
||||||
|
|
|
@ -57,9 +57,9 @@ func (rl *Instance) echo() {
|
||||||
|
|
||||||
// Print the input line with optional syntax highlighting
|
// Print the input line with optional syntax highlighting
|
||||||
if rl.SyntaxHighlighter != nil {
|
if rl.SyntaxHighlighter != nil {
|
||||||
print(rl.SyntaxHighlighter(line) + " ")
|
print(rl.SyntaxHighlighter(line))
|
||||||
} else {
|
} else {
|
||||||
print(string(line) + " ")
|
print(string(line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,14 +125,14 @@ func (rl *Instance) deleteX() {
|
||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) deleteBackspace() {
|
func (rl *Instance) deleteBackspace(forward bool) {
|
||||||
switch {
|
switch {
|
||||||
case len(rl.line) == 0:
|
case len(rl.line) == 0:
|
||||||
return
|
return
|
||||||
case rl.pos == 0:
|
case forward:
|
||||||
rl.line = rl.line[1:]
|
rl.line = append(rl.line[:rl.pos], rl.line[rl.pos+1:]...)
|
||||||
case rl.pos > len(rl.line):
|
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):
|
case rl.pos == len(rl.line):
|
||||||
rl.pos--
|
rl.pos--
|
||||||
rl.line = 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.line = rl.line[rl.pos:]
|
||||||
rl.pos = 0
|
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.
|
// It also calculates the runes in the string as well as any non-printable escape codes.
|
||||||
func (rl *Instance) SetPrompt(s string) {
|
func (rl *Instance) SetPrompt(s string) {
|
||||||
rl.mainPrompt = s
|
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,
|
// 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.
|
// We adjust cursor movement, depending on which mode we're currently in.
|
||||||
if !rl.modeTabCompletion {
|
if !rl.modeTabCompletion {
|
||||||
rl.tcUsedY = 1
|
rl.tcUsedY = 1
|
||||||
// Account for the hint line
|
// Account for the info line
|
||||||
} else if rl.modeTabCompletion && rl.modeAutoFind {
|
} else if rl.modeTabCompletion && rl.modeAutoFind {
|
||||||
rl.tcUsedY = 0
|
rl.tcUsedY = 0
|
||||||
} else {
|
} else {
|
||||||
|
@ -40,7 +47,7 @@ func (rl *Instance) RefreshPromptLog(log string) (err error) {
|
||||||
moveCursorUp(1)
|
moveCursorUp(1)
|
||||||
}
|
}
|
||||||
rl.stillOnRefresh = true
|
rl.stillOnRefresh = true
|
||||||
moveCursorUp(rl.hintY + rl.tcUsedY)
|
moveCursorUp(rl.infoY + rl.tcUsedY)
|
||||||
moveCursorBackwards(GetTermWidth())
|
moveCursorBackwards(GetTermWidth())
|
||||||
print("\r\n" + seqClearScreenBelow)
|
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.
|
// RefreshPromptInPlace - Refreshes the prompt in the very same place he is.
|
||||||
func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
||||||
|
|
||||||
// We adjust cursor movement, depending on which mode we're currently in.
|
// We adjust cursor movement, depending on which mode we're currently in.
|
||||||
// Prompt data intependent
|
// Prompt data intependent
|
||||||
if !rl.modeTabCompletion {
|
if !rl.modeTabCompletion {
|
||||||
rl.tcUsedY = 1
|
rl.tcUsedY = 1
|
||||||
// Account for the hint line
|
// Account for the info line
|
||||||
} else if rl.modeTabCompletion && rl.modeAutoFind {
|
} else if rl.modeTabCompletion && rl.modeAutoFind {
|
||||||
rl.tcUsedY = 0
|
rl.tcUsedY = 0
|
||||||
} else {
|
} else {
|
||||||
|
@ -82,7 +88,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
||||||
|
|
||||||
// Update the prompt if a special has been passed.
|
// Update the prompt if a special has been passed.
|
||||||
if prompt != "" {
|
if prompt != "" {
|
||||||
rl.mainPrompt = prompt
|
rl.SetPrompt(prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rl.Multiline {
|
if rl.Multiline {
|
||||||
|
@ -91,7 +97,7 @@ func (rl *Instance) RefreshPromptInPlace(prompt string) (err error) {
|
||||||
|
|
||||||
// Clear the input line and everything below
|
// Clear the input line and everything below
|
||||||
print(seqClearLine)
|
print(seqClearLine)
|
||||||
moveCursorUp(rl.hintY + rl.tcUsedY)
|
moveCursorUp(rl.infoY + rl.tcUsedY)
|
||||||
moveCursorBackwards(GetTermWidth())
|
moveCursorBackwards(GetTermWidth())
|
||||||
print("\r\n" + seqClearScreenBelow)
|
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.
|
// We adjust cursor movement, depending on which mode we're currently in.
|
||||||
if !rl.modeTabCompletion {
|
if !rl.modeTabCompletion {
|
||||||
rl.tcUsedY = 1
|
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
|
rl.tcUsedY = 0
|
||||||
} else {
|
} else {
|
||||||
rl.tcUsedY = 1
|
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.
|
// Update the prompt if a special has been passed.
|
||||||
if prompt != "" {
|
if prompt != "" {
|
||||||
rl.mainPrompt = prompt
|
rl.SetPrompt(prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new line if needed
|
// Add a new line if needed
|
||||||
|
@ -185,6 +191,7 @@ func (rl *Instance) computePrompt() (prompt []rune) {
|
||||||
|
|
||||||
// Strip color escapes
|
// Strip color escapes
|
||||||
rl.promptLen = getRealLength(string(rl.realPrompt))
|
rl.promptLen = getRealLength(string(rl.realPrompt))
|
||||||
|
rl.rightPromptLen = getRealLength(string(rl.rightPrompt))
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -205,3 +212,11 @@ func getRealLength(s string) (l int) {
|
||||||
stripped := ansi.Strip(s)
|
stripped := ansi.Strip(s)
|
||||||
return uniseg.GraphemeClusterCount(stripped)
|
return uniseg.GraphemeClusterCount(stripped)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rl *Instance) echoRightPrompt() {
|
||||||
|
if rl.fullX < GetTermWidth() - rl.rightPromptLen - 1 {
|
||||||
|
moveCursorForwards(GetTermWidth())
|
||||||
|
moveCursorBackwards(rl.rightPromptLen)
|
||||||
|
print(rl.rightPrompt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,9 +2,11 @@ package readline
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var rxMultiline = regexp.MustCompile(`[\r\n]+`)
|
var rxMultiline = regexp.MustCompile(`[\r\n]+`)
|
||||||
|
@ -38,11 +40,12 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
rl.modeViMode = VimInsert
|
rl.modeViMode = VimInsert
|
||||||
rl.pos = 0
|
rl.pos = 0
|
||||||
rl.posY = 0
|
rl.posY = 0
|
||||||
|
rl.tcPrefix = ""
|
||||||
|
|
||||||
// Completion && hints init
|
// Completion && infos init
|
||||||
rl.resetHintText()
|
rl.resetInfoText()
|
||||||
rl.resetTabCompletion()
|
rl.resetTabCompletion()
|
||||||
rl.getHintText()
|
rl.getInfoText()
|
||||||
|
|
||||||
// History Init
|
// History Init
|
||||||
// We need this set to the last command, so that we can access it quickly
|
// 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
|
return string(rl.line), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, print any hints or completions
|
// Finally, print any info or completions
|
||||||
// if the TabCompletion engines so desires
|
// if the TabCompletion engines so desires
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
|
|
||||||
|
@ -76,6 +79,12 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
var err error
|
var err error
|
||||||
i, err = os.Stdin.Read(b)
|
i, err = os.Stdin.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, syscall.EAGAIN) {
|
||||||
|
err = syscall.SetNonblock(syscall.Stdin, false)
|
||||||
|
if err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,8 +136,8 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ret.HintText) > 0 {
|
if len(ret.InfoText) > 0 {
|
||||||
rl.hintText = ret.HintText
|
rl.infoText = ret.InfoText
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
}
|
}
|
||||||
|
@ -160,9 +169,18 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
return "", CtrlC
|
return "", CtrlC
|
||||||
|
|
||||||
case charEOF:
|
case charEOF: // ctrl d
|
||||||
rl.clearHelpers()
|
if len(rl.line) == 0 {
|
||||||
return "", EOF
|
rl.clearHelpers()
|
||||||
|
return "", EOF
|
||||||
|
}
|
||||||
|
if rl.modeTabFind {
|
||||||
|
rl.backspaceTabFind()
|
||||||
|
} else {
|
||||||
|
if (rl.pos < len(rl.line)) {
|
||||||
|
rl.deleteBackspace(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clear screen
|
// Clear screen
|
||||||
case charCtrlL:
|
case charCtrlL:
|
||||||
|
@ -173,8 +191,8 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
}
|
}
|
||||||
print(seqClearScreenBelow)
|
print(seqClearScreenBelow)
|
||||||
|
|
||||||
rl.resetHintText()
|
rl.resetInfoText()
|
||||||
rl.getHintText()
|
rl.getInfoText()
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
|
|
||||||
// Line Editing ------------------------------------------------------------------------------------
|
// Line Editing ------------------------------------------------------------------------------------
|
||||||
|
@ -188,6 +206,16 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
rl.resetHelpers()
|
rl.resetHelpers()
|
||||||
rl.updateHelpers()
|
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:
|
case charBackspace, charBackspace2:
|
||||||
// When currently in history completion, we refresh and automatically
|
// When currently in history completion, we refresh and automatically
|
||||||
// insert the first (filtered) candidate, virtually
|
// insert the first (filtered) candidate, virtually
|
||||||
|
@ -213,7 +241,7 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
// Vim mode has different behaviors
|
// Vim mode has different behaviors
|
||||||
if rl.InputMode == Vim {
|
if rl.InputMode == Vim {
|
||||||
if rl.modeViMode == VimInsert {
|
if rl.modeViMode == VimInsert {
|
||||||
rl.backspace()
|
rl.backspace(false)
|
||||||
} else if rl.pos != 0 {
|
} else if rl.pos != 0 {
|
||||||
rl.pos--
|
rl.pos--
|
||||||
}
|
}
|
||||||
|
@ -222,7 +250,7 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Else emacs deletes a character
|
// Else emacs deletes a character
|
||||||
rl.backspace()
|
rl.backspace(false)
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,6 +415,10 @@ func (rl *Instance) Readline() (string, error) {
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case charCtrlUnderscore:
|
||||||
|
rl.undoLast()
|
||||||
|
rl.viUndoSkipAppend = true
|
||||||
|
|
||||||
case '\r':
|
case '\r':
|
||||||
fallthrough
|
fallthrough
|
||||||
case '\n':
|
case '\n':
|
||||||
|
@ -516,22 +548,27 @@ func (rl *Instance) editorInput(r []rune) {
|
||||||
|
|
||||||
case VimReplaceMany:
|
case VimReplaceMany:
|
||||||
for _, char := range r {
|
for _, char := range r {
|
||||||
rl.deleteX()
|
if rl.pos != len(rl.line) {
|
||||||
|
rl.deleteX()
|
||||||
|
}
|
||||||
rl.insert([]rune{char})
|
rl.insert([]rune{char})
|
||||||
}
|
}
|
||||||
rl.refreshVimStatus()
|
rl.refreshVimStatus()
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// For some reason Ctrl+k messes with the input line, so ignore it.
|
// Don't insert control keys
|
||||||
if r[0] == 11 {
|
if r[0] >= 1 && r[0] <= 31 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// We reset the history nav counter each time we come here:
|
// We reset the history nav counter each time we come here:
|
||||||
// We don't need it when inserting text.
|
// We don't need it when inserting text.
|
||||||
rl.histNavIdx = 0
|
rl.histNavIdx = 0
|
||||||
rl.insert(r)
|
rl.insert(r)
|
||||||
|
rl.writeHintText()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rl.echoRightPrompt()
|
||||||
|
|
||||||
if len(rl.multisplit) == 0 {
|
if len(rl.multisplit) == 0 {
|
||||||
rl.syntaxCompletion()
|
rl.syntaxCompletion()
|
||||||
}
|
}
|
||||||
|
@ -625,6 +662,8 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||||
}
|
}
|
||||||
rl.mainHist = true
|
rl.mainHist = true
|
||||||
rl.walkHistory(1)
|
rl.walkHistory(1)
|
||||||
|
moveCursorForwards(len(rl.line) - rl.pos)
|
||||||
|
rl.pos = len(rl.line)
|
||||||
|
|
||||||
case seqDown:
|
case seqDown:
|
||||||
if rl.modeTabCompletion {
|
if rl.modeTabCompletion {
|
||||||
|
@ -636,6 +675,8 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||||
}
|
}
|
||||||
rl.mainHist = true
|
rl.mainHist = true
|
||||||
rl.walkHistory(-1)
|
rl.walkHistory(-1)
|
||||||
|
moveCursorForwards(len(rl.line) - rl.pos)
|
||||||
|
rl.pos = len(rl.line)
|
||||||
|
|
||||||
case seqForwards:
|
case seqForwards:
|
||||||
if rl.modeTabCompletion {
|
if rl.modeTabCompletion {
|
||||||
|
@ -647,8 +688,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||||
}
|
}
|
||||||
if (rl.modeViMode == VimInsert && rl.pos < len(rl.line)) ||
|
if (rl.modeViMode == VimInsert && rl.pos < len(rl.line)) ||
|
||||||
(rl.modeViMode != VimInsert && rl.pos < len(rl.line)-1) {
|
(rl.modeViMode != VimInsert && rl.pos < len(rl.line)-1) {
|
||||||
moveCursorForwards(1)
|
rl.moveCursorByAdjust(1)
|
||||||
rl.pos++
|
|
||||||
}
|
}
|
||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
rl.viUndoSkipAppend = true
|
rl.viUndoSkipAppend = true
|
||||||
|
@ -663,10 +703,7 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||||
rl.renderHelpers()
|
rl.renderHelpers()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if rl.pos > 0 {
|
rl.moveCursorByAdjust(-1)
|
||||||
moveCursorBackwards(1)
|
|
||||||
rl.pos--
|
|
||||||
}
|
|
||||||
rl.viUndoSkipAppend = true
|
rl.viUndoSkipAppend = true
|
||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
|
|
||||||
|
@ -689,32 +726,64 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
return
|
return
|
||||||
case seqCtrlRightArrow:
|
case seqCtrlRightArrow:
|
||||||
|
rl.insert(rl.hintText)
|
||||||
rl.moveCursorByAdjust(rl.viJumpW(tokeniseLine))
|
rl.moveCursorByAdjust(rl.viJumpW(tokeniseLine))
|
||||||
rl.updateHelpers()
|
rl.updateHelpers()
|
||||||
return
|
return
|
||||||
|
|
||||||
case seqDelete:
|
case seqDelete,seqDelete2:
|
||||||
if rl.modeTabFind {
|
if rl.modeTabFind {
|
||||||
rl.backspaceTabFind()
|
rl.backspaceTabFind()
|
||||||
} else {
|
} else {
|
||||||
rl.deleteBackspace()
|
if (rl.pos < len(rl.line)) {
|
||||||
|
rl.deleteBackspace(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case seqHome, seqHomeSc:
|
case seqHome, seqHomeSc:
|
||||||
if rl.modeTabCompletion {
|
if rl.modeTabCompletion {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
moveCursorBackwards(rl.pos)
|
rl.moveCursorByAdjust(-rl.pos)
|
||||||
rl.pos = 0
|
rl.updateHelpers()
|
||||||
rl.viUndoSkipAppend = true
|
rl.viUndoSkipAppend = true
|
||||||
|
|
||||||
case seqEnd, seqEndSc:
|
case seqEnd, seqEndSc:
|
||||||
if rl.modeTabCompletion {
|
if rl.modeTabCompletion {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
moveCursorForwards(len(rl.line) - rl.pos)
|
rl.moveCursorByAdjust(len(rl.line) - rl.pos)
|
||||||
rl.pos = len(rl.line)
|
rl.updateHelpers()
|
||||||
rl.viUndoSkipAppend = true
|
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:
|
case seqAltR:
|
||||||
rl.resetVirtualComp(false)
|
rl.resetVirtualComp(false)
|
||||||
// For some modes only, if we are in vim Keys mode,
|
// 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.updateTabFind([]rune{})
|
||||||
rl.viUndoSkipAppend = true
|
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:
|
default:
|
||||||
if rl.modeTabFind {
|
if rl.modeTabFind {
|
||||||
return
|
return
|
||||||
|
@ -768,6 +867,8 @@ func (rl *Instance) escapeSeq(r []rune) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rl *Instance) carridgeReturn() {
|
func (rl *Instance) carridgeReturn() {
|
||||||
|
rl.moveCursorByAdjust(len(rl.line))
|
||||||
|
rl.updateHelpers()
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
print("\r\n")
|
print("\r\n")
|
||||||
if rl.HistoryAutoWrite {
|
if rl.HistoryAutoWrite {
|
||||||
|
|
|
@ -259,9 +259,9 @@ func (r *registers) resetRegister() {
|
||||||
// The user can show registers completions and insert, no matter the cursor position.
|
// The user can show registers completions and insert, no matter the cursor position.
|
||||||
func (rl *Instance) completeRegisters() (groups []*CompletionGroup) {
|
func (rl *Instance) completeRegisters() (groups []*CompletionGroup) {
|
||||||
|
|
||||||
// We set the hint exceptionally
|
// We set the info exceptionally
|
||||||
hint := BLUE + "-- registers --" + RESET
|
info := BLUE + "-- registers --" + RESET
|
||||||
rl.hintText = []rune(hint)
|
rl.infoText = []rune(info)
|
||||||
|
|
||||||
// Make the groups
|
// Make the groups
|
||||||
anonRegs := &CompletionGroup{
|
anonRegs := &CompletionGroup{
|
||||||
|
|
|
@ -73,7 +73,12 @@ func (rl *Instance) insertCandidate() {
|
||||||
|
|
||||||
// Ensure no indexing error happens with prefix
|
// Ensure no indexing error happens with prefix
|
||||||
if len(completion) >= 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 {
|
if !cur.TrimSlash && !cur.NoSpace {
|
||||||
rl.insert([]rune(" "))
|
rl.insert([]rune(" "))
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,19 +93,16 @@ func (rl *Instance) getTabSearchCompletion() {
|
||||||
}
|
}
|
||||||
rl.getCurrentGroup()
|
rl.getCurrentGroup()
|
||||||
|
|
||||||
// Set the hint for this completion mode
|
// Set the info for this completion mode
|
||||||
rl.hintText = append([]rune("Completion search: "), rl.tfLine...)
|
rl.infoText = append([]rune("Completion search: "), rl.tfLine...)
|
||||||
|
|
||||||
// Set the hint for this completion mode
|
|
||||||
rl.hintText = append([]rune("Completion search: "), rl.tfLine...)
|
|
||||||
|
|
||||||
for _, g := range rl.tcGroups {
|
for _, g := range rl.tcGroups {
|
||||||
g.updateTabFind(rl)
|
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 {
|
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.tcGroups = checkNilItems(rl.tcGroups) // Avoid nil maps in groups
|
||||||
rl.getCurrentGroup() // Make sure there is a current group
|
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 {
|
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))
|
"No command history source, or empty (Ctrl-G/Esc to cancel)", RESET))
|
||||||
rl.hintText = rl.histHint
|
rl.infoText = rl.histInfo
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the hint line with everything
|
// Set the info line with everything
|
||||||
rl.histHint = append([]rune("\033[38;5;183m"+string(rl.histHint)+RESET), rl.tfLine...)
|
rl.histInfo = append([]rune("\033[38;5;183m"+string(rl.histInfo)+RESET), rl.tfLine...)
|
||||||
rl.histHint = append(rl.histHint, []rune(RESET)...)
|
rl.histInfo = append(rl.histInfo, []rune(RESET)...)
|
||||||
rl.hintText = rl.histHint
|
rl.infoText = rl.histInfo
|
||||||
|
|
||||||
// Refresh filtered candidates
|
// Refresh filtered candidates
|
||||||
rl.tcGroups[0].updateTabFind(rl)
|
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 {
|
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
|
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:
|
// 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
|
// we will add a line to the end of the comps, giving the actualized
|
||||||
// number of completions remaining and not printed
|
// 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()
|
_, _, adjusted := rl.getCompletionCount()
|
||||||
remain := adjusted - offset
|
remain := adjusted - offset
|
||||||
if remain == 0 {
|
if remain == 0 {
|
||||||
return cropped, true
|
return cropped, true
|
||||||
}
|
}
|
||||||
hint := fmt.Sprintf(DIM+YELLOW+" %d more completions... (scroll down to show)"+RESET+"\n", remain)
|
info := fmt.Sprintf(DIM+YELLOW+" %d more completions... (scroll down to show)"+RESET+"\n", remain)
|
||||||
hinted = cropped + hint
|
infoed = cropped + info
|
||||||
return hinted, false
|
return infoed, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current absolute candidate position (prev groups x suggestions + curGroup.tcPosY)
|
// Get the current absolute candidate position (prev groups x suggestions + curGroup.tcPosY)
|
||||||
|
@ -512,7 +509,7 @@ func (rl *Instance) hasOneCandidate() bool {
|
||||||
// - The terminal lengh
|
// - The terminal lengh
|
||||||
// we use this function to prompt for confirmation before printing comps.
|
// we use this function to prompt for confirmation before printing comps.
|
||||||
func (rl *Instance) promptCompletionConfirm(sentence string) {
|
func (rl *Instance) promptCompletionConfirm(sentence string) {
|
||||||
rl.hintText = []rune(sentence)
|
rl.infoText = []rune(sentence)
|
||||||
|
|
||||||
rl.compConfirmWait = true
|
rl.compConfirmWait = true
|
||||||
rl.viUndoSkipAppend = true
|
rl.viUndoSkipAppend = true
|
||||||
|
|
|
@ -33,7 +33,7 @@ func (rl *Instance) updateTabFind(r []rune) {
|
||||||
var err error
|
var err error
|
||||||
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
|
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rl.hintText = []rune(Red("Failed to match search regexp"))
|
rl.infoText = []rune(Red("Failed to match search regexp"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// We update and print
|
// We update and print
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
|
import "golang.org/x/text/width"
|
||||||
|
|
||||||
// updateHelpers is a key part of the whole refresh process:
|
// 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
|
// and manage to get back to the current (computed) cursor coordinates
|
||||||
func (rl *Instance) updateHelpers() {
|
func (rl *Instance) updateHelpers() {
|
||||||
|
|
||||||
// Load all hints & completions before anything.
|
// Load all Infos & completions before anything.
|
||||||
// Thus overwrites anything having been dirtily added/forced/modified, like rl.SetHintText()
|
// Thus overwrites anything having been dirtily added/forced/modified, like rl.SetInfoText()
|
||||||
|
rl.getInfoText()
|
||||||
rl.getHintText()
|
rl.getHintText()
|
||||||
if rl.modeTabCompletion {
|
if rl.modeTabCompletion {
|
||||||
rl.getTabCompletion()
|
rl.getTabCompletion()
|
||||||
|
@ -20,6 +23,23 @@ func (rl *Instance) updateHelpers() {
|
||||||
rl.renderHelpers()
|
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)
|
// Update reference should be called only once in a "loop" (not Readline(), but key control loop)
|
||||||
func (rl *Instance) updateReferences() {
|
func (rl *Instance) updateReferences() {
|
||||||
|
|
||||||
|
@ -32,11 +52,11 @@ func (rl *Instance) updateReferences() {
|
||||||
|
|
||||||
var fullLine, cPosLine int
|
var fullLine, cPosLine int
|
||||||
if len(rl.currentComp) > 0 {
|
if len(rl.currentComp) > 0 {
|
||||||
fullLine = len(rl.lineComp)
|
fullLine = getWidth(rl.lineComp)
|
||||||
cPosLine = len(rl.lineComp[:rl.pos])
|
cPosLine = getWidth(rl.lineComp[:rl.pos])
|
||||||
} else {
|
} else {
|
||||||
fullLine = len(rl.line)
|
fullLine = getWidth(rl.line)
|
||||||
cPosLine = len(rl.line[:rl.pos])
|
cPosLine = getWidth(rl.line[:rl.pos])
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need the X offset of the whole line
|
// We need the X offset of the whole line
|
||||||
|
@ -46,6 +66,10 @@ func (rl *Instance) updateReferences() {
|
||||||
fullRest := toEndLine % GetTermWidth()
|
fullRest := toEndLine % GetTermWidth()
|
||||||
rl.fullX = fullRest
|
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
|
// Use rl.pos value to get the offset to go TO/FROM the CURRENT POSITION
|
||||||
lineToCursorPos := rl.promptLen + cPosLine
|
lineToCursorPos := rl.promptLen + cPosLine
|
||||||
offsetToCursor := lineToCursorPos / GetTermWidth()
|
offsetToCursor := lineToCursorPos / GetTermWidth()
|
||||||
|
@ -75,11 +99,11 @@ func (rl *Instance) resetHelpers() {
|
||||||
rl.modeAutoFind = false
|
rl.modeAutoFind = false
|
||||||
|
|
||||||
// Now reset all below-input helpers
|
// Now reset all below-input helpers
|
||||||
rl.resetHintText()
|
rl.resetInfoText()
|
||||||
rl.resetTabCompletion()
|
rl.resetTabCompletion()
|
||||||
}
|
}
|
||||||
|
|
||||||
// clearHelpers - Clears everything: prompt, input, hints & comps,
|
// clearHelpers - Clears everything: prompt, input, Infos & comps,
|
||||||
// and comes back at the prompt.
|
// and comes back at the prompt.
|
||||||
func (rl *Instance) clearHelpers() {
|
func (rl *Instance) clearHelpers() {
|
||||||
|
|
||||||
|
@ -97,25 +121,42 @@ func (rl *Instance) clearHelpers() {
|
||||||
moveCursorForwards(rl.posX)
|
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
|
// and replaces the cursor to its current position. This function never
|
||||||
// computes or refreshes any value, except from inside the echo function.
|
// computes or refreshes any value, except from inside the echo function.
|
||||||
func (rl *Instance) renderHelpers() {
|
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()
|
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
|
// Go at beginning of first line after input remainder
|
||||||
moveCursorDown(rl.fullY - rl.posY)
|
moveCursorDown(rl.fullY - rl.posY)
|
||||||
moveCursorBackwards(GetTermWidth())
|
moveCursorBackwards(GetTermWidth())
|
||||||
|
|
||||||
// Print hints, check for any confirmation hint current.
|
// Print Infos, check for any confirmation Info current.
|
||||||
// (do not overwrite the confirmation question hint)
|
// (do not overwrite the confirmation question Info)
|
||||||
if !rl.compConfirmWait {
|
if !rl.compConfirmWait {
|
||||||
if len(rl.hintText) > 0 {
|
if len(rl.infoText) > 0 {
|
||||||
print("\n")
|
print("\n")
|
||||||
}
|
}
|
||||||
rl.writeHintText()
|
rl.writeInfoText()
|
||||||
moveCursorBackwards(GetTermWidth())
|
moveCursorBackwards(GetTermWidth())
|
||||||
|
|
||||||
// Print completions and go back to beginning of this line
|
// 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
|
// If we are still waiting for the user to confirm too long completions
|
||||||
// Immediately refresh the hints
|
// Immediately refresh the Infos
|
||||||
if rl.compConfirmWait {
|
if rl.compConfirmWait {
|
||||||
print("\n")
|
print("\n")
|
||||||
rl.writeHintText()
|
rl.writeInfoText()
|
||||||
rl.getHintText()
|
rl.getInfoText()
|
||||||
moveCursorBackwards(GetTermWidth())
|
moveCursorBackwards(GetTermWidth())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anyway, compensate for hint printout
|
// Anyway, compensate for Info printout
|
||||||
if len(rl.hintText) > 0 {
|
if len(rl.infoText) > 0 {
|
||||||
moveCursorUp(rl.hintY)
|
moveCursorUp(rl.infoY)
|
||||||
} else if !rl.compConfirmWait {
|
} else if !rl.compConfirmWait {
|
||||||
moveCursorUp(1)
|
moveCursorUp(1)
|
||||||
} else if rl.compConfirmWait {
|
} else if rl.compConfirmWait {
|
||||||
|
|
|
@ -399,22 +399,22 @@ func (rl *Instance) refreshVimStatus() {
|
||||||
rl.updateHelpers()
|
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.
|
// 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 {
|
switch rl.modeViMode {
|
||||||
case VimKeys:
|
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:
|
case VimInsert:
|
||||||
rl.hintText = []rune("-- INSERT --")
|
rl.infoText = []rune("-- INSERT --")
|
||||||
case VimReplaceOnce:
|
case VimReplaceOnce:
|
||||||
rl.hintText = []rune("-- REPLACE CHARACTER --")
|
rl.infoText = []rune("-- REPLACE CHARACTER --")
|
||||||
case VimReplaceMany:
|
case VimReplaceMany:
|
||||||
rl.hintText = []rune("-- REPLACE --")
|
rl.infoText = []rune("-- REPLACE --")
|
||||||
case VimDelete:
|
case VimDelete:
|
||||||
rl.hintText = []rune("-- DELETE --")
|
rl.infoText = []rune("-- DELETE --")
|
||||||
default:
|
default:
|
||||||
rl.getHintText()
|
rl.getInfoText()
|
||||||
}
|
}
|
||||||
|
|
||||||
rl.clearHelpers()
|
rl.clearHelpers()
|
||||||
|
|
|
@ -33,7 +33,7 @@ func (rl *Instance) viDelete(r rune) {
|
||||||
rl.saveBufToRegister(rl.line)
|
rl.saveBufToRegister(rl.line)
|
||||||
rl.clearLine()
|
rl.clearLine()
|
||||||
rl.resetHelpers()
|
rl.resetHelpers()
|
||||||
rl.getHintText()
|
rl.getInfoText()
|
||||||
|
|
||||||
case 'e':
|
case 'e':
|
||||||
vii := rl.getViIterations()
|
vii := rl.getViIterations()
|
||||||
|
|
264
rl.go
264
rl.go
|
@ -5,23 +5,26 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"hilbish/util"
|
||||||
|
|
||||||
"github.com/maxlandon/readline"
|
"github.com/maxlandon/readline"
|
||||||
"github.com/yuin/gopher-lua"
|
rt "github.com/arnodel/golua/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type lineReader struct {
|
type lineReader struct {
|
||||||
rl *readline.Instance
|
rl *readline.Instance
|
||||||
}
|
}
|
||||||
var fileHist *fileHistory
|
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 {
|
func newLineReader(prompt string, noHist bool) *lineReader {
|
||||||
rl := readline.NewInstance()
|
rl := readline.NewInstance()
|
||||||
// we don't mind hilbish.read rl instances having completion,
|
// we don't mind hilbish.read rl instances having completion,
|
||||||
// but it cant have shared history
|
// but it cant have shared history
|
||||||
if !noHist {
|
if !noHist {
|
||||||
fileHist = newFileHistory()
|
fileHist = newFileHistory()
|
||||||
rl.SetHistoryCtrlR("file", fileHist)
|
rl.SetHistoryCtrlR("History", fileHist)
|
||||||
rl.HistoryAutoWrite = false
|
rl.HistoryAutoWrite = false
|
||||||
}
|
}
|
||||||
rl.ShowVimMode = false
|
rl.ShowVimMode = false
|
||||||
|
@ -44,9 +47,45 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||||
}
|
}
|
||||||
hooks.Em.Emit("hilbish.vimAction", actionStr, args)
|
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) {
|
rl.TabCompleter = func(line []rune, pos int, _ readline.DelayedTabContext) (string, []*readline.CompletionGroup) {
|
||||||
ctx := string(line)
|
ctx := string(line)
|
||||||
var completions []string
|
|
||||||
|
|
||||||
var compGroup []*readline.CompletionGroup
|
var compGroup []*readline.CompletionGroup
|
||||||
|
|
||||||
|
@ -75,23 +114,20 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||||
return prefix, compGroup
|
return prefix, compGroup
|
||||||
} else {
|
} else {
|
||||||
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
|
if completecb, ok := luaCompletions["command." + fields[0]]; ok {
|
||||||
luaFields := l.NewTable()
|
luaFields := rt.NewTable()
|
||||||
for _, f := range fields {
|
for i, f := range fields {
|
||||||
luaFields.Append(lua.LString(f))
|
luaFields.Set(rt.IntValue(int64(i + 1)), rt.StringValue(f))
|
||||||
}
|
}
|
||||||
err := l.CallByParam(lua.P{
|
|
||||||
Fn: completecb,
|
// we must keep the holy 80 cols
|
||||||
NRet: 1,
|
luacompleteTable, err := rt.Call1(l.MainThread(),
|
||||||
Protect: true,
|
rt.FunctionValue(completecb), rt.StringValue(query),
|
||||||
}, lua.LString(query), lua.LString(ctx), luaFields)
|
rt.StringValue(ctx), rt.TableValue(luaFields))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", compGroup
|
return "", compGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
luacompleteTable := l.Get(-1)
|
|
||||||
l.Pop(1)
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
as an example with git,
|
as an example with git,
|
||||||
completion table should be structured like:
|
completion table should be structured like:
|
||||||
|
@ -116,60 +152,98 @@ func newLineReader(prompt string, noHist bool) *lineReader {
|
||||||
it is the responsibility of the completer
|
it is the responsibility of the completer
|
||||||
to work on subcommands and subcompletions
|
to work on subcommands and subcompletions
|
||||||
*/
|
*/
|
||||||
if cmpTbl, ok := luacompleteTable.(*lua.LTable); ok {
|
if cmpTbl, ok := luacompleteTable.TryTable(); ok {
|
||||||
cmpTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
nextVal := rt.NilValue
|
||||||
if key.Type() == lua.LTNumber {
|
for {
|
||||||
// completion group
|
next, val, ok := cmpTbl.Next(nextVal)
|
||||||
if value.Type() == lua.LTTable {
|
if next == rt.NilValue {
|
||||||
luaCmpGroup := value.(*lua.LTable)
|
break
|
||||||
compType := luaCmpGroup.RawGet(lua.LString("type"))
|
}
|
||||||
compItems := luaCmpGroup.RawGet(lua.LString("items"))
|
nextVal = next
|
||||||
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())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var dispType readline.TabDisplayType
|
_, ok = next.TryInt()
|
||||||
switch compType.String() {
|
valTbl, okk := val.TryTable()
|
||||||
case "grid": dispType = readline.TabDisplayGrid
|
if !ok || !okk {
|
||||||
case "list": dispType = readline.TabDisplayList
|
// TODO: error?
|
||||||
// need special cases, will implement later
|
break
|
||||||
//case "map": dispType = readline.TabDisplayMap
|
}
|
||||||
|
|
||||||
|
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{
|
items = append(items, nxStr)
|
||||||
DisplayType: dispType,
|
itemDescription, ok := vlTbl.Get(rt.IntValue(1)).TryString()
|
||||||
Descriptions: itemDescriptions,
|
if !ok {
|
||||||
Suggestions: items,
|
// TODO: error
|
||||||
TrimSlash: false,
|
continue
|
||||||
NoSpace: true,
|
}
|
||||||
})
|
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 {
|
if len(compGroup) == 0 {
|
||||||
completions = fileComplete(query, ctx, fields)
|
completions, p := fileComplete(query, ctx, fields)
|
||||||
compGroup = append(compGroup, &readline.CompletionGroup{
|
fcompGroup := []*readline.CompletionGroup{{
|
||||||
TrimSlash: false,
|
TrimSlash: false,
|
||||||
NoSpace: true,
|
NoSpace: true,
|
||||||
Suggestions: completions,
|
Suggestions: completions,
|
||||||
})
|
}}
|
||||||
|
|
||||||
|
return p, fcompGroup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", compGroup
|
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) {
|
func (lr *lineReader) AddHistory(cmd string) {
|
||||||
fileHist.Write(cmd)
|
fileHist.Write(cmd)
|
||||||
}
|
}
|
||||||
|
@ -221,56 +302,65 @@ func (lr *lineReader) Resize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// lua module
|
// lua module
|
||||||
func (lr *lineReader) Loader(L *lua.LState) *lua.LTable {
|
func (lr *lineReader) Loader(rtm *rt.Runtime) *rt.Table {
|
||||||
lrLua := map[string]lua.LGFunction{
|
lrLua := map[string]util.LuaExport{
|
||||||
"add": lr.luaAddHistory,
|
"add": {lr.luaAddHistory, 1, false},
|
||||||
"all": lr.luaAllHistory,
|
"all": {lr.luaAllHistory, 0, false},
|
||||||
"clear": lr.luaClearHistory,
|
"clear": {lr.luaClearHistory, 0, false},
|
||||||
"get": lr.luaGetHistory,
|
"get": {lr.luaGetHistory, 1, false},
|
||||||
"size": lr.luaSize,
|
"size": {lr.luaSize, 0, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := l.SetFuncs(l.NewTable(), lrLua)
|
mod := rt.NewTable()
|
||||||
|
util.SetExports(rtm, mod, lrLua)
|
||||||
|
|
||||||
return mod
|
return mod
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lr *lineReader) luaAddHistory(l *lua.LState) int {
|
func (lr *lineReader) luaAddHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
cmd := l.CheckString(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmd, err := c.StringArg(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
lr.AddHistory(cmd)
|
lr.AddHistory(cmd)
|
||||||
|
|
||||||
return 0
|
return c.Next(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lr *lineReader) luaSize(L *lua.LState) int {
|
func (lr *lineReader) luaSize(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
L.Push(lua.LNumber(fileHist.Len()))
|
return c.PushingNext1(t.Runtime, rt.IntValue(int64(fileHist.Len()))), nil
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lr *lineReader) luaGetHistory(L *lua.LState) int {
|
func (lr *lineReader) luaGetHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
idx := L.CheckInt(1)
|
if err := c.Check1Arg(); err != nil {
|
||||||
cmd, _ := fileHist.GetLine(idx)
|
return nil, err
|
||||||
L.Push(lua.LString(cmd))
|
}
|
||||||
|
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 {
|
func (lr *lineReader) luaAllHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
tbl := L.NewTable()
|
tbl := rt.NewTable()
|
||||||
size := fileHist.Len()
|
size := fileHist.Len()
|
||||||
|
|
||||||
for i := 1; i < size; i++ {
|
for i := 1; i < size; i++ {
|
||||||
cmd, _ := fileHist.GetLine(i)
|
cmd, _ := fileHist.GetLine(i)
|
||||||
tbl.Append(lua.LString(cmd))
|
tbl.Set(rt.IntValue(int64(i)), rt.StringValue(cmd))
|
||||||
}
|
}
|
||||||
|
|
||||||
L.Push(tbl)
|
return c.PushingNext1(t.Runtime, rt.TableValue(tbl)), nil
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lr *lineReader) luaClearHistory(l *lua.LState) int {
|
func (lr *lineReader) luaClearHistory(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
return 0
|
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
|
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.
|
// Document adds a documentation string to a module.
|
||||||
// It is accessible via the __doc metatable.
|
// It is accessible via the __doc metatable.
|
||||||
func Document(L *lua.LState, module lua.LValue, doc string) {
|
func Document(module *rt.Table, doc string) {
|
||||||
mt := L.GetMetatable(module)
|
mt := module.Metatable()
|
||||||
if mt == lua.LNil {
|
|
||||||
mt = L.NewTable()
|
if mt == nil {
|
||||||
L.SetMetatable(module, mt)
|
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.
|
// 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.
|
// 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) {
|
func SetField(rtm *rt.Runtime, module *rt.Table, field string, value rt.Value, doc string) {
|
||||||
mt := L.GetMetatable(module)
|
// TODO: ^ rtm isnt needed, i should remove it
|
||||||
if mt == lua.LNil {
|
mt := module.Metatable()
|
||||||
mt = L.NewTable()
|
|
||||||
docProp := L.NewTable()
|
|
||||||
L.SetField(mt, "__docProp", docProp)
|
|
||||||
|
|
||||||
L.SetMetatable(module, mt)
|
if mt == nil {
|
||||||
|
mt = rt.NewTable()
|
||||||
|
docProp := rt.NewTable()
|
||||||
|
mt.Set(rt.StringValue("__docProp"), rt.TableValue(docProp))
|
||||||
|
|
||||||
|
module.SetMetatable(mt)
|
||||||
}
|
}
|
||||||
docProp := L.GetTable(mt, lua.LString("__docProp"))
|
docProp := mt.Get(rt.StringValue("__docProp"))
|
||||||
|
|
||||||
L.SetField(docProp, field, lua.LString(doc))
|
docProp.AsTable().Set(rt.StringValue(field), rt.StringValue(doc))
|
||||||
L.SetField(module, field, value)
|
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
|
// String vars that are free to be changed at compile time
|
||||||
var (
|
var (
|
||||||
version = "v1.0.4"
|
version = "v2.0.0"
|
||||||
defaultConfDir = "" // ~ will be substituted for home, path for user's default config
|
|
||||||
defaultHistDir = ""
|
defaultHistDir = ""
|
||||||
commonRequirePaths = "';./libs/?/init.lua;./?/init.lua;./?/?.lua'"
|
commonRequirePaths = "';./libs/?/init.lua;./?/init.lua;./?/?.lua'"
|
||||||
|
|
||||||
|
|
|
@ -17,4 +17,5 @@ var (
|
||||||
dataDir = "/usr/local/share/hilbish"
|
dataDir = "/usr/local/share/hilbish"
|
||||||
preloadPath = dataDir + "/prelude/init.lua"
|
preloadPath = dataDir + "/prelude/init.lua"
|
||||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
||||||
|
defaultConfDir = getenv("XDG_CONFIG_HOME", "~/.config")
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,4 +17,5 @@ var (
|
||||||
dataDir = "/usr/share/hilbish"
|
dataDir = "/usr/share/hilbish"
|
||||||
preloadPath = dataDir + "/prelude/init.lua"
|
preloadPath = dataDir + "/prelude/init.lua"
|
||||||
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
sampleConfPath = dataDir + "/.hilbishrc.lua" // Path to default/sample config
|
||||||
|
defaultConfDir = ""
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,4 +11,5 @@ var (
|
||||||
dataDir = "~\\Appdata\\Roaming\\Hilbish" // ~ and \ gonna cry?
|
dataDir = "~\\Appdata\\Roaming\\Hilbish" // ~ and \ gonna cry?
|
||||||
preloadPath = dataDir + "\\prelude\\init.lua"
|
preloadPath = dataDir + "\\prelude\\init.lua"
|
||||||
sampleConfPath = dataDir + "\\hilbishrc.lua" // Path to default/sample config
|
sampleConfPath = dataDir + "\\hilbishrc.lua" // Path to default/sample config
|
||||||
|
defaultConfDir = ""
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue