Compare commits

...

34 Commits

Author SHA1 Message Date
sammyette 856d3197d1
feat: store current idx of history search 2023-11-11 21:14:47 -04:00
sammyette 504e9b272a
chore: merge from master 2023-11-11 20:44:22 -04:00
sammyette 29e14c1aee
feat: native modules (#257) 2023-11-11 19:39:31 -05:00
Super ee1ed077b9
fix: infinite loop when navigating history without any history (#268)
fixes #252
2023-11-02 17:50:36 -04:00
sammyette 78eb657897
feat: hilbish native pager (#240) 2023-10-25 00:41:53 -04:00
sammyette a5b6fc8eda
fix: return cmdstr in alias resolve instead of nothing 2023-09-30 23:25:15 -04:00
sammyette 4421869b85
fix: check length of args in aliases resolve
this shouldnt be a problem.. but ... what
2023-09-30 23:21:00 -04:00
sammyette 6d07d8db53
chore: merge from upstream 2023-09-30 19:53:38 -04:00
sammyette 483e5f6dbe
fix(hilbish.completions): return prefix on the `call` function (from #263) 2023-09-30 19:52:33 -04:00
sammyette 83492e4e69
docs: fix odd sentence in runner-mode docs 2023-09-27 20:31:27 -04:00
sammyette 4fcd310048
chore: merge from upstream 2023-09-09 13:53:22 -04:00
sammyette db8e87e5dd
fix: compare lower case strings in contains function
this fixes file completion on windows.
2023-09-09 13:53:09 -04:00
sammyette ba04fb2af7
feat: allow numbered substitutions in aliases (#258) 2023-09-09 13:51:58 -04:00
sammyette caff604d95
ci: stop using deprecated method of setting branch name 2023-07-10 22:42:04 -04:00
sammyette 2b9059f726
ci: add missing quote 2023-07-10 22:38:22 -04:00
sammyette 00c967d7c1
ci: fix branch name 2023-07-10 22:38:13 -04:00
TorchedSammy ab26b82a9f docs: [ci] generate new docs 2023-07-11 01:57:00 +00:00
sammyette f540fc2c04
feat: show go version hilbish was compiled with 2023-07-10 21:56:35 -04:00
sammyette c6c81ddece
feat: add notification/message interface (#239)
details in #219
2023-07-10 19:03:30 -04:00
sammyette 3eae0f07be
feat: add fuzzy searching for completion and history search (#247)
* feat: add fuzzy searching for completion and history search

* feat: add fuzzy opt for fuzzy history searching

* chore: add fuzzy opt to changelog
2023-07-10 00:06:29 -04:00
sammyette cbc5e81c9d
feat: show indexes on cdr list and change home to tilde (#253)
* feat: show indexes on cdr list and change home to tilde

* fix: remove underline in cdr help page
2023-07-09 18:43:21 -04:00
TorchedSammy 6729ecddea docs: [ci] generate new docs 2023-04-15 14:36:56 +00:00
sammyette 485306fbe2
docs: clarify use of highlighter function 2023-04-15 10:36:21 -04:00
sammyette 480d4de750
ci: set baseurl properly 2023-04-14 01:25:14 -04:00
sammyette d7dde44a0c
ci: (attempt to) fix base url on non-master versions 2023-04-14 01:23:24 -04:00
sammyette 237b306eb8
ci: fix branch name var 2023-04-14 00:52:45 -04:00
sammyette bd01b478d5
website: add more info for runner mode 2023-04-14 00:40:41 -04:00
sammyette a37f8e3b30
ci: output website builds from other branches to different dirs 2023-04-14 00:40:13 -04:00
sammyette 9c6d6c4f31
website: fix wording on features main page 2023-04-14 00:24:19 -04:00
sammyette 2dbd201e1e
refactor: website/branding cleanup (#250)
* website: add padding to home page

* docs: slight touch ups to readme

* website: reword/reformat content on home and getting started page

* website: fix docs page layout

- the sidenav for doc pages looks the same as the navbar.
- it will be hidden by default on mobile.
- sidenav looks like the navbar on mobile and is more
seamless in general

* docs: update description, logo, dont center badges

* docs: fix badges

* website: fix padding

* website: add borders, add margins for sidenav

* website: update description on homepage

* website: add margins for blog list

* chore: use new logo

* docs: use logo and text combined for readme

* docs: make logo bigger

* website: use combined logo and text asset

* docs: remove hilbish-text asset

* website: fix navbar expand button

* website: add more padding on doc pages

* website(blog): add post about website improvements

* website(blog): add post about website improvements

* website: add more detail on the install page

* website: add screenshots section

* docs: fix up screenshots section

* docs: remove toc and image alignment
2023-04-14 00:04:52 -04:00
sammyette 3dd834fcdf
fix: merge patch releases with master (#248)
my absolutely terrible april fools release was made on a separate branch. this pr merges the fixes on master so history can be synced up. (and mainly to get the 2.1.2 blog post)
2023-04-10 12:38:30 -04:00
sammyette 659d1e0c4e
website(blog): add v2.1.1 release blog 2023-04-01 18:16:13 -04:00
TorchedSammy 0379e302ac docs: [ci] generate new docs 2023-03-25 21:42:46 +00:00
sammyette d577b3c958
feat: sink enhancements (#241)
* feat: add read method to sinks

* feat: add manual flush method for sinks

* fix: add flush as sink method and fix arg amount for read

* feat: add pipe property to sinks to see if stdin sink is a pipe

* feat: add method autoFlush to toggle/change automatic flushing

* chore: update changelog
2023-03-25 17:42:25 -04:00
69 changed files with 1696 additions and 215 deletions

View File

@ -1,10 +1,8 @@
name: Build website
on:
push:
branches:
- master
- docs-refactor
- push
- pull_request
jobs:
deploy:
@ -21,11 +19,29 @@ jobs:
hugo-version: 'latest'
extended: true
- name: Set branch name
id: branch
run: echo "BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> "$GITHUB_ENV"
- name: Fix base URL
if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea'
run: sed -i "s%baseURL = 'https://rosettea.github.io/Hilbish/'%baseURL = 'https://rosettea.github.io/Hilbish/versions/${{ env.BRANCH_NAME }}'%" website/config.toml
- name: Build
run: 'cd website && hugo --minify'
- name: Deploy
if: env.BRANCH_NAME == 'master' && github.repository_owner == 'Rosettea'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./website/public
keep_files: true
- name: Deploy
if: env.BRANCH_NAME != 'master' && github.repository_owner == 'Rosettea'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./website/public
destination_dir: versions/${{ env.BRANCH_NAME }}
keep_files: true

View File

@ -1,18 +1,39 @@
-- Default Hilbish config
local hilbish = require 'hilbish'
local lunacolors = require 'lunacolors'
local bait = require 'bait'
local ansikit = require 'ansikit'
local unreadCount = 0
local running = false
local function doPrompt(fail)
hilbish.prompt(lunacolors.format(
'{blue}%u {cyan}%d ' .. (fail and '{red}' or '{green}') .. ''
))
end
local function doNotifyPrompt()
if running or unreadCount == hilbish.messages.unreadCount() then return end
local notifPrompt = string.format('• %s unread notification%s', hilbish.messages.unreadCount(), hilbish.messages.unreadCount() > 1 and 's' or '')
unreadCount = hilbish.messages.unreadCount()
hilbish.prompt(lunacolors.blue(notifPrompt), 'right')
hilbish.timeout(function()
hilbish.prompt('', 'right')
end, 3000)
end
doPrompt()
bait.catch('command.preexec', function()
running = true
end)
bait.catch('command.exit', function(code)
running = false
doPrompt(code ~= 0)
doNotifyPrompt()
end)
bait.catch('hilbish.vimMode', function(mode)
@ -22,3 +43,7 @@ bait.catch('hilbish.vimMode', function(mode)
ansikit.cursorStyle(ansikit.lineCursor)
end
end)
bait.catch('hilbish.notification', function(notif)
doNotifyPrompt()
end)

View File

@ -1,9 +1,41 @@
# 🎀 Changelog
## Unreleased
### Added
- Made a few additions to the sink type:
- `read()` method for retrieving input (so now the `in` sink of commanders is useful)
- `flush()` and `autoFlush()` related to flushing outputs
- `pipe` property to check if a sink with input is a pipe (like stdin)
- Add fuzzy search to history search (enable via `hilbish.opts.fuzzy = true`)
- Show indexes on cdr list
- Fix doc command not displaying correct subdocs when using shorthand api doc access (`doc api hilbish.jobs` as an example)
- `hilbish.messages` interface (details in [#219])
- `hilbish.notification` signal when a message/notification is sent
- `notifyJobFinish` opt to send a notification when background jobs are
completed.
- Allow numbered arg substitutions in aliases.
- Example: `hilbish.alias('hello', 'echo %1 says hello')` allows the user to run `hello hilbish`
which will output `hilbish says hello`.
- Greenhouse
- Greenhouse is a pager library and program. Basic usage is `greenhouse <file>`
- Using this also brings enhancements to the `doc` command like easy
navigation of neighboring doc files.
### Fixed
- Fix infinite loop when navigating history without any history. [#252](https://github.com/Rosettea/Hilbish/issues/252)
- Return the prefix when calling `hilbish.completions.call`. [#219](https://github.com/Rosettea/Hilbish/issues/219)
- Replaced `sed` in-place editing with `grep` and `mv` for compatibility with BSD utils
## [2.1.2] - 2022-04-10
### Removed
- Bad april fools code ;(
## [2.1.1] - 2022-04-01
### Added
- Validation checks for command input
- Improved runtime performance
- Validate Lua code
## [2.1.0] - 2022-02-10
### Added
- Documented custom userdata types (Job and Timer Objects)

View File

@ -1,45 +1,35 @@
<div align="center">
<img src="./assets/hilbish-flower.png" width=128><br>
<img src="./assets/hilbish-text.png" width=256><br>
<blockquote>
🌺 The flower shell. A comfy and nice little shell for Lua fans!
</blockquote>
<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 commits since latest release (by date)" src="https://img.shields.io/github/commits-since/Rosettea/Hilbish/latest?style=flat-square">
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors/Rosettea/Hilbish?style=flat-square"><br>
<a href="https://github.com/Rosettea/Hilbish/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22"><img src="https://img.shields.io/github/issues/Hilbis/Hilbish/help%20wanted?style=flat-square&color=green" alt="help wanted"></a>
<a href="https://github.com/Rosettea/Hilbish/blob/master/LICENSE"><img alt="GitHub license" src="https://img.shields.io/github/license/Rosettea/Hilbish?style=flat-square"></a>
<a href="https://discord.gg/3PDdcQz"><img alt="Discord" src="https://img.shields.io/discord/732357621503229962?color=blue&style=flat-square"></a>
</p>
</div>
<img src="./assets/hilbish-logo-and-text.png" width=512><br>
<blockquote>
🌓 The Moon-powered shell! A comfy and extensible shell for Lua fans! 🌺 ✨
</blockquote>
Hilbish is a extensible shell (framework). It was made to be very customizable
via the Lua programming language. It aims to be easy to use for the casual
people but powerful for those who want to tinker more with their shell,
the thing used to interface with most of the system.
<img alt="GitHub commit activity" src="https://img.shields.io/github/commit-activity/m/Rosettea/Hilbish?style=flat-square"><img alt="GitHub commits since latest release (by date)" src="https://img.shields.io/github/commits-since/Rosettea/Hilbish/latest?style=flat-square"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/Rosettea/Hilbish?style=flat-square"><br>
<a href="https://github.com/Rosettea/Hilbish/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22"><img src="https://img.shields.io/github/issues/Hilbis/Hilbish/help%20wanted?style=flat-square&color=green" alt="help wanted"></a>
<a href="https://github.com/Rosettea/Hilbish/blob/master/LICENSE"><img alt="GitHub license" src="https://img.shields.io/github/license/Rosettea/Hilbish?style=flat-square"></a>
<a href="https://discord.gg/3PDdcQz"><img alt="Discord" src="https://img.shields.io/discord/732357621503229962?color=blue&style=flat-square"></a>
<br>
Hilbish is an extensible shell designed to be highly customizable.
It is configured in Lua and provides a good range of features.
It aims to be easy to use for anyone but powerful enough for
those who need it.
The motivation for choosing Lua was that its simpler and better to use
than old shell script. It's fine for basic interactive shell uses,
but that's the only place Hilbish has shell script; everything else is Lua
and aims to be infinitely configurable. If something isn't, open an issue!
# Table of Contents
- [Screenshots](#Screenshots)
- [Getting Hilbish](#Getting-Hilbish)
- [Contributing](#Contributing)
# Screenshots
<div align="center">
<img src="gallery/terminal.png"><br><br>
<img src="gallery/tab.png"><br><br>
<img src="gallery/tab.png">
<img src="gallery/pillprompt.png">
</div>
# Getting Hilbish
**NOTE:** Hilbish is not guaranteed to work properly on Windows, starting
from the 2.0 version. It will still be able to compile, but functionality
may be lacking.
may be lacking. If you want to contribute to make the situation better,
comment on the Windows discussion.
You can check the [install page](https://rosettea.github.io/Hilbish/install/)
on the website for distributed binaries from GitHub or other package repositories.

View File

@ -13,12 +13,22 @@ vars:
tasks:
default:
cmds:
- go build {{.GOFLAGS}}
vars:
GOFLAGS: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}} -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"'
default-nocgo:
cmds:
- CGO_ENABLED=0 go build {{.GOFLAGS}}
vars:
GOFLAGS: '-ldflags "-s -w -X main.dataDir={{.LIBDIR}} -X main.gitCommit=$(git rev-parse --short HEAD) -X main.gitBranch=$(git rev-parse --abbrev-ref HEAD)"'
build:
cmds:
- go build {{.GOFLAGS}}
build-nocgo:
cmds:
- CGO_ENABLED=0 go build {{.GOFLAGS}}

View File

@ -1,6 +1,8 @@
package main
import (
"regexp"
"strconv"
"strings"
"sync"
@ -46,9 +48,32 @@ func (a *aliasModule) Resolve(cmdstr string) string {
a.mu.RLock()
defer a.mu.RUnlock()
args := strings.Split(cmdstr, " ")
arg, _ := regexp.Compile(`[\\]?%\d+`)
args, _ := splitInput(cmdstr)
if len(args) == 0 {
// this shouldnt reach but...????
return cmdstr
}
for a.aliases[args[0]] != "" {
alias := a.aliases[args[0]]
alias = arg.ReplaceAllStringFunc(alias, func(a string) string {
idx, _ := strconv.Atoi(a[1:])
if strings.HasPrefix(a, "\\") || idx == 0 {
return strings.TrimPrefix(a, "\\")
}
if idx + 1 > len(args) {
return a
}
val := args[idx]
args = cut(args, idx)
cmdstr = strings.Join(args, " ")
return val
})
cmdstr = alias + strings.TrimPrefix(cmdstr, args[0])
cmdArgs, _ := splitInput(cmdstr)
args = cmdArgs

13
api.go
View File

@ -2,6 +2,7 @@
// The Hilbish module includes the core API, containing
// interfaces and functions which directly relate to shell functionality.
// #field ver The version of Hilbish
// #field goVersion The version of Go that Hilbish was compiled with
// #field user Username of the user
// #field host Hostname of the machine
// #field dataDir Directory for Hilbish data files, including the docs and default modules
@ -110,6 +111,7 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
}
util.SetFieldProtected(fakeMod, mod, "ver", rt.StringValue(getVersion()))
util.SetFieldProtected(fakeMod, mod, "goVersion", rt.StringValue(runtime.Version()))
util.SetFieldProtected(fakeMod, mod, "user", rt.StringValue(username))
util.SetFieldProtected(fakeMod, mod, "host", rt.StringValue(host))
util.SetFieldProtected(fakeMod, mod, "home", rt.StringValue(curuser.HomeDir))
@ -164,6 +166,9 @@ func hilbishLoad(rtm *rt.Runtime) (rt.Value, func()) {
util.SetField(rtm, versionModule, "release", rt.StringValue(releaseName))
mod.Set(rt.StringValue("version"), rt.TableValue(versionModule))
pluginModule := moduleLoader(rtm)
mod.Set(rt.StringValue("module"), rt.TableValue(pluginModule))
return rt.TableValue(fakeMod), nil
}
@ -639,6 +644,14 @@ func hlhinter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
// reality could set the input of the prompt to *display* anything. The
// callback is passed the current line and is expected to return a line that
// will be used as the input display.
// Note that to set a highlighter, one has to override this function.
// Example:
// ```
// function hilbish.highlighter(line)
// return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
// end
// ```
// This code will highlight all double quoted strings in green.
// --- @param line string
func hlhighlighter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.Next(), nil

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -417,7 +417,7 @@ func main() {
f, _ := os.Create(docPath)
f.WriteString(fmt.Sprintf(header, modOrIface, modname, modu.ShortDescription))
typeTag, _ := regexp.Compile(`@\w+`)
typeTag, _ := regexp.Compile(`\B@\w+`)
modDescription := typeTag.ReplaceAllStringFunc(strings.Replace(modu.Description, "<", `\<`, -1), func(typ string) string {
typName := typ[1:]
typLookup := typeTable[strings.ToLower(typName)]

View File

@ -253,15 +253,16 @@ func callLuaCompleter(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
// we must keep the holy 80 cols
completerReturn, err := rt.Call1(l.MainThread(),
rt.FunctionValue(completecb), rt.StringValue(query),
rt.StringValue(ctx), rt.TableValue(fields))
cont := c.Next()
err = rt.Call(l.MainThread(), rt.FunctionValue(completecb),
[]rt.Value{rt.StringValue(query), rt.StringValue(ctx), rt.TableValue(fields)},
cont)
if err != nil {
return nil, err
}
return c.PushingNext1(t.Runtime, completerReturn), nil
return cont, nil
}
// #interface completions

View File

@ -13,6 +13,7 @@ interfaces and functions which directly relate to shell functionality.
## Interface fields
- `ver`: The version of Hilbish
- `goVersion`: The version of Go that Hilbish was compiled with
- `user`: Username of the user
- `host`: Hostname of the machine
- `dataDir`: Directory for Hilbish data files, including the docs and default modules
@ -49,6 +50,14 @@ Line highlighter handler. This is mainly for syntax highlighting, but in
reality could set the input of the prompt to *display* anything. The
callback is passed the current line and is expected to return a line that
will be used as the input display.
Note that to set a highlighter, one has to override this function.
Example:
```
function hilbish.highlighter(line)
return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
end
```
This code will highlight all double quoted strings in green.
### hinter(line, pos)
The command line hint handler. It gets called on every key insert to
@ -109,6 +118,19 @@ A sink is a structure that has input and/or output to/from
a desination.
### Methods
#### autoFlush(auto)
Sets/toggles the option of automatically flushing output.
A call with no argument will toggle the value.
#### flush()
Flush writes all buffered input to the sink.
#### read() -> string
Reads a liine of input from the sink.
#### readAll() -> string
Reads all input from the sink.
#### write(str)
Writes data to a sink.

View File

@ -21,6 +21,10 @@ Returns the text that is at the register.
### insert(text)
Inserts text into the line.
### getChar() -> string
Reads a keystroke from the user. This is in a format
of something like Ctrl-L..
### setVimRegister(register, text)
Sets the vim register at `register` to hold the passed text.

View File

@ -0,0 +1,53 @@
---
title: Interface hilbish.module
description: native module loading
layout: doc
menu:
docs:
parent: "API"
---
## Introduction
The hilbish.module interface provides a function to load
Hilbish plugins/modules. Hilbish modules are Go-written
plugins (see https://pkg.go.dev/plugin) that are used to add functionality
to Hilbish that cannot be written in Lua for any reason.
Note that you don't ever need to use the load function that is here as
modules can be loaded with a `require` call like Lua C modules, and the
search paths can be changed with the `paths` property here.
To make a valid native module, the Go plugin has to export a Loader function
with a signature like so: `func(*rt.Runtime) rt.Value`.
`rt` in this case refers to the Runtime type at
https://pkg.go.dev/github.com/arnodel/golua@master/runtime#Runtime
Hilbish uses this package as its Lua runtime. You will need to read
it to use it for a native plugin.
Here is some code for an example plugin:
```go
package main
import (
rt "github.com/arnodel/golua/runtime"
)
func Loader(rtm *rt.Runtime) rt.Value {
return rt.StringValue("hello world!")
}
```
This can be compiled with `go build -buildmode=plugin plugin.go`.
If you attempt to require and print the result (`print(require 'plugin')`), it will show "hello world!"
## Interface fields
- `paths`: A list of paths to search when loading native modules. This is in the style of Lua search paths and will be used when requiring native modules. Example: `?.so;?/?.so`
## Functions
### load(path)
Loads a module at the designated `path`.
It will throw if any error occurs.

View File

@ -1,3 +1,8 @@
+ `command.preexec` -> input, cmdStr > Thrown before a command
is executed. The `input` is the user written command, while `cmdStr`
is what will be executed (`input` will have aliases while `cmdStr`
will have alias resolved input).
+ `command.exit` -> code, cmdStr > Thrown when a command exits.
`code` is the exit code of the command, and `cmdStr` is the command that was run.

View File

@ -7,3 +7,6 @@
like yanking or pasting text. See `doc vim-mode actions` for more info.
+ `hilbish.cancel` > Sent when the user cancels their input with Ctrl-C.
+ `hilbish.notification` -> message > Sent when a message is
sent.

View File

@ -16,6 +16,7 @@ func editorLoader(rtm *rt.Runtime) *rt.Table {
"setVimRegister": {editorSetRegister, 1, false},
"getVimRegister": {editorGetRegister, 2, false},
"getLine": {editorGetLine, 0, false},
"readChar": {editorReadChar, 0, false},
}
mod := rt.NewTable()
@ -94,3 +95,13 @@ func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
}
// #interface editor
// getChar() -> string
// Reads a keystroke from the user. This is in a format
// of something like Ctrl-L..
func editorReadChar(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
buf := lr.rl.ReadChar()
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
}

View File

@ -40,6 +40,10 @@ function hilbish.editor.getVimRegister(register) end
--- Inserts text into the line.
function hilbish.editor.insert(text) end
--- Reads a keystroke from the user. This is in a format
--- of something like Ctrl-L..
function hilbish.editor.getChar() end
--- Sets the vim register at `register` to hold the passed text.
--- @param register string
--- @param text string
@ -79,6 +83,14 @@ function hilbish.goro(fn) end
--- reality could set the input of the prompt to *display* anything. The
--- callback is passed the current line and is expected to return a line that
--- will be used as the input display.
--- Note that to set a highlighter, one has to override this function.
--- Example:
--- ```
--- function hilbish.highlighter(line)
--- return line:gsub('"%w+"', function(c) return lunacolors.green(c) end)
--- end
--- ```
--- This code will highlight all double quoted strings in green.
--- @param line string
function hilbish.highlighter(line) end
@ -180,6 +192,22 @@ function hilbish.jobs:foreground() end
--- @param cmd string
function hilbish.runner.lua(cmd) end
--- Sets/toggles the option of automatically flushing output.
--- A call with no argument will toggle the value.
--- @param auto boolean|nil
function hilbish:autoFlush(auto) end
--- Flush writes all buffered input to the sink.
function hilbish:flush() end
--- Reads a liine of input from the sink.
--- @returns string
function hilbish:read() end
--- Reads all input from the sink.
--- @returns string
function hilbish:readAll() end
--- Writes data to a sink.
function hilbish:write(str) end
@ -192,6 +220,10 @@ function hilbish.jobs:start() end
--- Stops the job from running.
function hilbish.jobs:stop() end
--- Loads a module at the designated `path`.
--- It will throw if any error occurs.
function hilbish.module.load(path) end
--- Runs a command in Hilbish's shell script interpreter.
--- This is the equivalent of using `source`.
--- @param cmd string

3
go.mod
View File

@ -5,9 +5,9 @@ go 1.17
require (
github.com/arnodel/golua v0.0.0-20220221163911-dfcf252b6f86
github.com/blackfireio/osinfo v1.0.3
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9
github.com/maxlandon/readline v0.1.0-beta.0.20211027085530-2b76cabb8036
github.com/pborman/getopt v1.1.0
github.com/sahilm/fuzzy v0.1.0
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171
mvdan.cc/sh/v3 v3.5.1
@ -17,6 +17,7 @@ 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/kylelemons/godebug v1.1.0 // 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-20220513210516-0976fa681c29 // indirect

20
go.sum
View File

@ -1,13 +1,5 @@
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac h1:dtXrgjch8PQyf7C90anZUquB5U3dr8AcMGJofeuirrI=
github.com/Rosettea/golua v0.0.0-20220419183026-6d22d6fec5ac/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
github.com/Rosettea/golua v0.0.0-20220518005949-116371948fe3 h1:I/wWr40FFLFF9pbT3wLb1FAEZhKb/hUWE+nJ5uHBK2g=
github.com/Rosettea/golua v0.0.0-20220518005949-116371948fe3/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
github.com/Rosettea/golua v0.0.0-20220621002945-b05143999437 h1:6lWu4YVLeKuZ8jR9xwHONhkHBsrIbw5dpfG1gtOVw0A=
github.com/Rosettea/golua v0.0.0-20220621002945-b05143999437/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345 h1:QNYjYDogUSiNUkffbhFSrSCtpZhofeiVYGFN2FI4wSs=
github.com/Rosettea/golua v0.0.0-20221213193027-cbf6d4e4d345/go.mod h1:9jzpYPiU2is0HVGCiuIOBSXdergHUW44IEjmuN1UrIE=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e h1:P2XupP8SaylWaudD1DqbWtZ3mIa8OsE9635LmR+Q+lg=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220306140409-795a84b00b4e/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b h1:s5eDMhBk6H1BgipgLub/gv9qeyBaTuiHM0k3h2/9TSE=
github.com/Rosettea/sh/v3 v3.4.0-0.dev.0.20220524215627-dfd9a4fa219b/go.mod h1:R09vh/04ILvP2Gj8/Z9Jd0Dh0ZIvaucowMEs6abQpWs=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
@ -18,8 +10,6 @@ github.com/arnodel/strftime v0.1.6/go.mod h1:5NbK5XqYK8QpRZpqKNt4OlxLtIB8cotkLk4
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/blackfireio/osinfo v1.0.3 h1:Yk2t2GTPjBcESv6nDSWZKO87bGMQgO+Hi9OoXPpxX8c=
github.com/blackfireio/osinfo v1.0.3/go.mod h1:Pd987poVNmd5Wsx6PRPw4+w7kLlf9iJxoRKPtPAjOrA=
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9 h1:xz6Nv3zcwO2Lila35hcb0QloCQsc38Al13RNEzWRpX4=
github.com/chuckpreslar/emission v0.0.0-20170206194824-a7ddd980baf9/go.mod h1:2wSM9zJkl1UQEFZgSd68NfCgRz1VL1jzy/RjCg+ULrs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.15 h1:cKRCLMj3Ddm54bKSpemfQ8AtYFBhAI2MPmdys22fBdc=
github.com/creack/pty v1.1.15/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
@ -36,6 +26,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=
@ -49,7 +41,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451 h1:d1PiN4RxzIFXCJTvRkvSkKqwtRAl5ZV4lATKtQI0B7I=
github.com/rogpeppe/go-internal v1.8.1-0.20210923151022-86f73c517451/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -58,18 +51,13 @@ golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7w
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-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210916214954-140adaaadfaf/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/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/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=

2
lua.go
View File

@ -68,7 +68,7 @@ func luaInit() {
}
// Add more paths that Lua can require from
err := util.DoString(l, "package.path = package.path .. " + requirePaths)
_, err := util.DoString(l, "package.path = package.path .. " + requirePaths)
if err != nil {
fmt.Fprintln(os.Stderr, "Could not add Hilbish require paths! Libraries will be missing. This shouldn't happen.")
}

View File

@ -106,7 +106,7 @@ func main() {
}
if *verflag {
fmt.Printf("Hilbish %s\n", getVersion())
fmt.Printf("Hilbish %s\nCompiled with %s\n", getVersion(), runtime.Version())
os.Exit(0)
}
@ -289,7 +289,7 @@ func removeDupes(slice []string) []string {
func contains(s []string, e string) bool {
for _, a := range s {
if a == e {
if strings.ToLower(a) == strings.ToLower(e) {
return true
}
}
@ -324,3 +324,7 @@ func getVersion() string {
return v.String()
}
func cut(slice []string, idx int) []string {
return append(slice[:idx], slice[idx + 1:]...)
}

92
module.go 100644
View File

@ -0,0 +1,92 @@
package main
import (
"plugin"
"hilbish/util"
rt "github.com/arnodel/golua/runtime"
)
// #interface module
// native module loading
// #field paths A list of paths to search when loading native modules. This is in the style of Lua search paths and will be used when requiring native modules. Example: `?.so;?/?.so`
/*
The hilbish.module interface provides a function to load
Hilbish plugins/modules. Hilbish modules are Go-written
plugins (see https://pkg.go.dev/plugin) that are used to add functionality
to Hilbish that cannot be written in Lua for any reason.
Note that you don't ever need to use the load function that is here as
modules can be loaded with a `require` call like Lua C modules, and the
search paths can be changed with the `paths` property here.
To make a valid native module, the Go plugin has to export a Loader function
with a signature like so: `func(*rt.Runtime) rt.Value`.
`rt` in this case refers to the Runtime type at
https://pkg.go.dev/github.com/arnodel/golua@master/runtime#Runtime
Hilbish uses this package as its Lua runtime. You will need to read
it to use it for a native plugin.
Here is some code for an example plugin:
```go
package main
import (
rt "github.com/arnodel/golua/runtime"
)
func Loader(rtm *rt.Runtime) rt.Value {
return rt.StringValue("hello world!")
}
```
This can be compiled with `go build -buildmode=plugin plugin.go`.
If you attempt to require and print the result (`print(require 'plugin')`), it will show "hello world!"
*/
func moduleLoader(rtm *rt.Runtime) *rt.Table {
exports := map[string]util.LuaExport{
"load": {moduleLoad, 2, false},
}
mod := rt.NewTable()
util.SetExports(rtm, mod, exports)
return mod
}
// #interface module
// load(path)
// Loads a module at the designated `path`.
// It will throw if any error occurs.
func moduleLoad(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.CheckNArgs(1); err != nil {
return nil, err
}
path, err := c.StringArg(0)
if err != nil {
return nil, err
}
p, err := plugin.Open(path)
if err != nil {
return nil, err
}
value, err := p.Lookup("Loader")
if err != nil {
return nil, err
}
loader, ok := value.(func(*rt.Runtime) rt.Value)
if !ok {
return nil, nil
}
val := loader(t.Runtime)
return c.PushingNext1(t.Runtime, val), nil
}

View File

@ -10,7 +10,7 @@ cdr: change directory to one which has been recently visied
usage: cdr <index>
to get a list of recent directories, use {green}{underline}cdr list{reset}]])
to get a list of recent directories, use {green}cdr list{reset}]])
return
end
@ -20,7 +20,10 @@ to get a list of recent directories, use {green}{underline}cdr list{reset}]])
sinks.out:writeln 'No directories have been visited.'
return 1
end
sinks.out:writeln(table.concat(recentDirs, '\n'))
for idx, d in ipairs(dirs.recentDirs) do
if d:find(hilbish.home, 1, true) then d = fs.join('~', d:sub(hilbish.home:len() + 1)) end
sinks.out:writeln(lunacolors.format(string.format('{cyan}%d{reset} %s', idx, d)))
end
return
end

View File

@ -1,6 +1,9 @@
local ansikit = require 'ansikit'
local commander = require 'commander'
local fs = require 'fs'
local lunacolors = require 'lunacolors'
local Greenhouse = require 'nature.greenhouse'
local Page = require 'nature.greenhouse.page'
commander.register('doc', function(args, sinks)
local moddocPath = hilbish.dataDir .. '/docs/'
@ -9,11 +12,6 @@ commander.register('doc', function(args, sinks)
-- hilbish git
moddocPath = './docs/'
end
local apidocHeader = [[
# %s
{grayBg} {white}{italic}%s {reset}
]]
local modules = table.map(fs.readdir(moddocPath), function(f)
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', '')))
@ -25,47 +23,15 @@ to Hilbish.
Usage: doc <section> [subdoc]
Available sections: ]] .. table.concat(modules, ', ')
if #args > 0 then
local mod = args[1]
local f = io.open(moddocPath .. mod .. '.md', 'rb')
local funcdocs = nil
local subdocName = args[2]
if not f then
-- assume subdir
-- dataDir/docs/<mod>/<mod>.md
moddocPath = moddocPath .. mod .. '/'
if not subdocName then
subdocName = '_index'
end
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
if not f then
f = io.open(moddocPath .. subdocName:match '%w+' .. '/' .. subdocName .. '.md', 'rb')
end
if not f then
moddocPath = moddocPath .. subdocName .. '/'
subdocName = args[3] or '_index'
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
end
if not f then
sinks.out:writeln('No documentation found for ' .. mod .. '.')
return 1
end
end
funcdocs = f:read '*a':gsub('-([%d]+)', '%1')
local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= '_index.md' and f ~= 'index.md' end)
local subdocs = table.map(moddocs, function(fname)
return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.md', '')))
end)
if #moddocs ~= 0 then
funcdocs = funcdocs .. '\nSubdocs: ' .. table.concat(subdocs, ', ')
end
local valsStr = funcdocs:match '%-%-%-\n([^%-%-%-]+)\n'
local f
local function handleYamlInfo(d)
local vals = {}
local docs = d
local valsStr = docs:match '%-%-%-\n([^%-%-%-]+)\n'
print(valsStr)
if valsStr then
local _, endpos = funcdocs:find('---\n' .. valsStr .. '\n---\n\n', 1, true)
funcdocs = funcdocs:sub(endpos + 1, #funcdocs)
docs = docs:sub(valsStr:len() + 10, #docs)
-- parse vals
local lines = string.split(valsStr, '\n')
@ -78,23 +44,113 @@ Available sections: ]] .. table.concat(modules, ', ')
end
end
end
if mod == 'api' then
funcdocs = string.format(apidocHeader, vals.title, vals.description or 'no description.') .. funcdocs
end
doc = funcdocs:sub(1, #funcdocs - 1)
f:close()
--docs = docs:sub(1, #docs - 1)
return docs, vals
end
local backtickOccurence = 0
sinks.out:writeln(lunacolors.format(doc:gsub('`', function()
backtickOccurence = backtickOccurence + 1
if backtickOccurence % 2 == 0 then
return '{reset}'
else
return '{underline}{green}'
if #args > 0 then
local mod = args[1]
f = io.open(moddocPath .. mod .. '.md', 'rb')
local funcdocs = nil
local subdocName = args[2]
if not f then
moddocPath = moddocPath .. mod .. '/'
if not subdocName then
subdocName = '_index'
end
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
local oldmoddocPath = moddocPath
if not f then
moddocPath = moddocPath .. subdocName:match '%w+' .. '/'
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
end
if not f then
moddocPath = oldmoddocPath .. subdocName .. '/'
subdocName = args[3] or '_index'
f = io.open(moddocPath .. subdocName .. '.md', 'rb')
end
if not f then
sinks.out:writeln('No documentation found for ' .. mod .. '.')
return 1
end
end
end):gsub('\n#+.-\n', function(t)
local signature = t:gsub('<.->(.-)</.->', '{underline}%1'):gsub('\\', '<')
return '{bold}{yellow}' .. signature .. '{reset}'
end)))
end
local moddocs = table.filter(fs.readdir(moddocPath), function(f) return f ~= '_index.md' and f ~= 'index.md' end)
local subdocs = table.map(moddocs, function(fname)
return lunacolors.underline(lunacolors.blue(string.gsub(fname, '.md', '')))
end)
local gh = Greenhouse(sinks.out)
function gh:resize()
local size = terminal.size()
self.region = {
width = size.width,
height = size.height - 3
}
end
gh:resize()
function gh:render()
local workingPage = self.pages[self.curPage]
local offset = self.offset
if self.isSpecial then
offset = self.specialOffset
workingPage = self.specialPage
end
self.sink:write(ansikit.getCSI(self.region.height + 2 .. ';1', 'H'))
if not self.isSpecial then
if args[1] == 'api' then
self.sink:writeln(lunacolors.reset(string.format('%s', workingPage.title)))
self.sink:write(lunacolors.format(string.format('{grayBg} ↳ {white}{italic}%s {reset}', workingPage.description or 'No description.')))
else
self.sink:write(lunacolors.reset(string.format('Viewing doc page %s', moddocPath)))
end
end
end
local backtickOccurence = 0
local function formatDocText(d)
return lunacolors.format(d:gsub('`', function()
backtickOccurence = backtickOccurence + 1
if backtickOccurence % 2 == 0 then
return '{reset}'
else
return '{underline}{green}'
end
end):gsub('\n#+.-\n', function(t)
local signature = t:gsub('<.->(.-)</.->', '{underline}%1'):gsub('\\', '<')
return '{bold}{yellow}' .. signature .. '{reset}'
end))
end
local doc, vals = handleYamlInfo(#args == 0 and doc or formatDocText(f:read '*a':gsub('-([%d]+)', '%1')))
if #moddocs ~= 0 and f then
doc = doc .. '\nSubdocs: ' .. table.concat(subdocs, ', ') .. '\n\n'
end
if f then f:close() end
local page = Page(vals.title, doc)
page.description = vals.description
gh:addPage(page)
-- add subdoc pages
for _, sdName in ipairs(moddocs) do
local sdFile = fs.join(sdName, '_index.md')
if sdName:match '.md$' then
sdFile = sdName
end
local f = io.open(moddocPath .. sdFile, 'rb')
local doc, vals = handleYamlInfo(f:read '*a':gsub('-([%d]+)', '%1'))
local page = Page(vals.title, formatDocText(doc))
page.description = vals.description
gh:addPage(page)
end
ansikit.hideCursor()
gh:initUi()
end)

View File

@ -0,0 +1,124 @@
local ansikit = require 'ansikit'
local bait = require 'bait'
local commander = require 'commander'
local hilbish = require 'hilbish'
local lunacolors = require 'lunacolors'
local terminal = require 'terminal'
local Greenhouse = require 'nature.greenhouse'
local Page = require 'nature.greenhouse.page'
commander.register('greenhouse', function(args, sinks)
local gh = Greenhouse(sinks.out)
local buffer = ''
local display = ''
local command = false
local commands = {
q = function()
gh.keybinds['Ctrl-D'](gh)
end,
['goto'] = function(args)
if not args[1] then
return 'nuh uh'
end
gh:jump(tonumber(args[1]))
end
}
function gh:resize()
local size = terminal.size()
self.region = {
width = size.width,
height = size.height - 2
}
end
function gh:render()
local workingPage = self.pages[self.curPage]
local offset = self.offset
if self.isSpecial then
offset = self.specialOffset
workingPage = self.specialPage
end
self.sink:write(ansikit.getCSI(self.region.height + 1 .. ';1', 'H'))
if not self.isSpecial then
self.sink:writeln(lunacolors.format(string.format('{grayBg} ↳ Page %d%s{reset}', self.curPage, workingPage.title and '' .. workingPage.title .. ' ' or '')))
end
self.sink:write(buffer == '' and display or buffer)
end
function gh:input(c)
-- command handling
if c == ':' and not command then
command = true
end
if c == 'Escape' then
if command then
command = false
buffer = ''
else
if self.isSpecial then gh:special() end
end
elseif c == 'Backspace' then
buffer = buffer:sub(0, -2)
if buffer == '' then
command = false
else
goto update
end
end
if command then
ansikit.showCursor()
if buffer:match '^:' then buffer = buffer .. c else buffer = c end
else
ansikit.hideCursor()
end
::update::
gh:update()
end
gh:resize()
gh:keybind('Enter', function(self)
if self.isSpecial then
self:jump(self.specialPageIdx)
self:special(false)
else
if buffer:len() < 2 then return end
local splitBuf = string.split(buffer, " ")
local command = commands[splitBuf[1]:sub(2)]
if command then
table.remove(splitBuf, 1)
buffer = command(splitBuf) or ''
end
self:update()
end
end)
if sinks['in'].pipe then
local page = Page('stdin', sinks['in']:readAll())
gh:addPage(page)
end
for _, name in ipairs(args) do
local f <close> = io.open(name, 'r')
if not f then
sinks.err:writeln(string.format('could not open file %s', name))
end
local page = Page(name, f:read '*a')
gh:addPage(page)
end
if #gh.pages == 0 then
sinks.out:writeln [[greenhouse is the Hilbish pager library and command!
usage: greenhouse <file>...
example: greenhouse hello.md]]
return 1
end
ansikit.hideCursor()
gh:initUi()
end)

View File

@ -0,0 +1,328 @@
-- Greenhouse is a simple text scrolling handler for terminal programs.
-- The idea is that it can be set a region to do its scrolling and paging
-- job and then the user can draw whatever outside it.
-- This reduces code duplication for the message viewer
-- and flowerbook.
local ansikit = require 'ansikit'
local lunacolors = require 'lunacolors'
local terminal = require 'terminal'
local Page = require 'nature.greenhouse.page'
local Object = require 'nature.object'
local Greenhouse = Object:extend()
function Greenhouse:new(sink)
local size = terminal.size()
self.region = size
self.contents = nil -- or can be a table
self.start = 1 -- where to start drawing from (should replace with self.region.y)
self.offset = 1 -- vertical text offset
self.sink = sink
self.pages = {}
self.curPage = 1
self.keybinds = {
['Up'] = function(self) self:scroll 'up' end,
['Down'] = function(self) self:scroll 'down' end,
['Ctrl-Left'] = self.previous,
['Ctrl-Right'] = self.next,
['Ctrl-N'] = function(self) self:toc(true) end,
['Enter'] = function(self)
if self.isSpecial then
self:jump(self.specialPageIdx)
self:special(false)
end
end
}
self.isSpecial = false
self.specialPage = nil
self.specialPageIdx = 1
self.specialOffset = 1
return self
end
function Greenhouse:addPage(page)
table.insert(self.pages, page)
end
function Greenhouse:updateCurrentPage(text)
local page = self.pages[self.curPage]
page:setText(text)
end
local function sub(str, limit)
local overhead = 0
local function addOverhead(s)
overhead = overhead + string.len(s)
end
local s = str:gsub('\x1b%[%d+;%d+;%d+;%d+;%d+%w', addOverhead)
:gsub('\x1b%[%d+;%d+;%d+;%d+%w', addOverhead)
:gsub('\x1b%[%d+;%d+;%d+%w',addOverhead)
:gsub('\x1b%[%d+;%d+%w', addOverhead)
:gsub('\x1b%[%d+%w', addOverhead)
return s:sub(0, limit + overhead)
end
function Greenhouse:draw()
local workingPage = self.pages[self.curPage]
local offset = self.offset
if self.isSpecial then
offset = self.specialOffset
workingPage = self.specialPage
end
if workingPage.lazy and not workingPage.loaded then
workingPage.initialize()
end
local lines = workingPage.lines
self.sink:write(ansikit.getCSI(self.start .. ';1', 'H'))
self.sink:write(ansikit.getCSI(2, 'J'))
for i = offset, offset + self.region.height - 1 do
if i > #lines then break end
local writer = self.sink.writeln
if i == offset + self.region.height - 1 then writer = self.sink.write end
writer(self.sink, sub(lines[i]:gsub('\t', ' '), self.region.width))
end
self:render()
end
function Greenhouse:render()
end
function Greenhouse:scroll(direction)
if self.isSpecial then
if direction == 'down' then
self:next(true)
elseif direction == 'up' then
self:previous(true)
end
return
end
local lines = self.pages[self.curPage].lines
local oldOffset = self.offset
if direction == 'down' then
self.offset = math.min(self.offset + 1, math.max(1, #lines - self.region.height))
elseif direction == 'up' then
self.offset = math.max(self.offset - 1, 1)
end
if self.offset ~= oldOffset then self:draw() end
end
function Greenhouse:update()
self:resize()
if self.isSpecial then
self:updateSpecial()
end
self:draw()
end
function Greenhouse:special(val)
self.isSpecial = val
self:update()
end
function Greenhouse:toggleSpecial()
self:special(not self.isSpecial)
end
--- This function will be called when the special page
--- is on and needs to be updated.
function Greenhouse:updateSpecial()
end
function Greenhouse:contents()
end
function Greenhouse:toc(toggle)
if not self.isSpecial then
self.specialPageIdx = self.curPage
end
if toggle then self.isSpecial = not self.isSpecial end
-- Generate a special page for our table of contents
local tocText = string.format([[
%s
]], lunacolors.cyan(lunacolors.bold '―― Table of Contents ――'))
local genericPageCount = 1
local contents = self:contents()
if contents then
for i, c in ipairs(contents) do
local title = c.title
if c.active then
title = lunacolors.invert(title)
end
tocText = tocText .. title .. '\n'
end
else
for i, page in ipairs(self.pages) do
local title = page.title
if title == 'Page' then
title = 'Page #' .. genericPageCount
genericPageCount = genericPageCount + 1
end
if i == self.specialPageIdx then
title = lunacolors.invert(title)
end
tocText = tocText .. title .. '\n'
end
end
self.specialPage = Page('TOC', tocText)
function self:updateSpecial()
self:toc()
end
self:draw()
end
function Greenhouse:resize()
local size = terminal.size()
self.region = size
end
function Greenhouse:next(special)
local oldCurrent = special and self.specialPageIdx or self.curPage
local pageIdx = math.min(oldCurrent + 1, #self.pages)
if special then
self.specialPageIdx = pageIdx
else
self.curPage = pageIdx
end
if pageIdx ~= oldCurrent then
self.offset = 1
self:update()
end
end
function Greenhouse:previous(special)
local oldCurrent = special and self.specialPageIdx or self.curPage
local pageIdx = math.max(self.curPage - 1, 1)
if special then
self.specialPageIdx = pageIdx
else
self.curPage = pageIdx
end
if pageIdx ~= oldCurrent then
self.offset = 1
self:update()
end
end
function Greenhouse:jump(idx)
if idx ~= self.curPage then
self.offset = 1
end
self.curPage = idx
self:update()
end
function Greenhouse:keybind(key, callback)
self.keybinds[key] = callback
end
function Greenhouse:input(char)
end
function Greenhouse:initUi()
local ansikit = require 'ansikit'
local bait = require 'bait'
local commander = require 'commander'
local hilbish = require 'hilbish'
local terminal = require 'terminal'
local Page = require 'nature.greenhouse.page'
local done = false
bait.catch('signal.sigint', function()
ansikit.clear()
done = true
end)
bait.catch('signal.resize', function()
self:update()
end)
ansikit.screenAlt()
ansikit.clear(true)
self:draw()
hilbish.goro(function()
while not done do
local c = read()
self:keybind('Ctrl-D', function()
done = true
end)
if self.keybinds[c] then
self.keybinds[c](self)
else
self:input(c)
end
--[[
if c == 27 then
local c1 = read()
if c1 == 91 then
local c2 = read()
if c2 == 66 then -- arrow down
self:scroll 'down'
elseif c2 == 65 then -- arrow up
self:scroll 'up'
end
if c2 == 49 then
local c3 = read()
if c3 == 59 then
local c4 = read()
if c4 == 53 then
local c5 = read()
if c5 == 67 then
self:next()
elseif c5 == 68 then
self:previous()
end
end
end
end
end
goto continue
end
]]--
::continue::
end
end)
while not done do
--
end
ansikit.showCursor()
ansikit.screenMain()
end
function read()
terminal.saveState()
terminal.setRaw()
local c = hilbish.editor.readChar()
terminal.restoreState()
return c
end
return Greenhouse

View File

@ -0,0 +1,32 @@
local Object = require 'nature.object'
local Page = Object:extend()
function Page:new(title, text)
self:setText(text)
self.title = title or 'Page'
self.lazy = false
self.loaded = true
self.children = {}
end
function Page:setText(text)
self.lines = string.split(text, '\n')
end
function Page:setTitle(title)
self.title = title
end
function Page:dynamic(initializer)
self.initializer = initializer
self.lazy = true
self.loaded = false
end
function Page:initialize()
self.initializer()
self.loaded = true
end
return Page

View File

@ -0,0 +1,84 @@
local bait = require 'bait'
local commander = require 'commander'
local lunacolors = require 'lunacolors'
local M = {}
local counter = 0
local unread = 0
M._messages = {}
M.icons = {
INFO = '',
SUCCESS = '',
WARN = '',
ERROR = ''
}
hilbish.messages = {}
--- Represents a Hilbish message.
--- @class hilbish.message
--- @field icon string Unicode (preferably standard emoji) icon for the message notification.
--- @field title string Title of the message (like an email subject).
--- @field text string Contents of the message.
--- @field channel string Short identifier of the message. `hilbish` and `hilbish.*` is preserved for internal Hilbish messages.
--- @field summary string A short summary of the message.
--- @field read boolean Whether the full message has been read or not.
function expect(tbl, field)
if not tbl[field] or tbl[field] == '' then
error(string.format('expected field %s in message'))
end
end
--- Sends a message.
--- @param message hilbish.message
function hilbish.messages.send(message)
expect(message, 'text')
expect(message, 'title')
counter = counter + 1
unread = unread + 1
message.index = counter
message.read = false
M._messages[message.index] = message
bait.throw('hilbish.notification', message)
end
function hilbish.messages.read(idx)
local msg = M._messages[idx]
if msg then
M._messages[idx].read = true
unread = unread - 1
end
end
function hilbish.messages.readAll(idx)
for _, msg in ipairs(hilbish.messages.all()) do
hilbish.messages.read(msg.index)
end
end
function hilbish.messages.unreadCount()
return unread
end
function hilbish.messages.delete(idx)
local msg = M._messages[idx]
if not msg then
error(string.format('invalid message index %d', idx or -1))
end
M._messages[idx] = nil
end
function hilbish.messages.clear()
for _, msg in ipairs(hilbish.messages.all()) do
hilbish.messages.delete(msg.index)
end
end
function hilbish.messages.all()
return M._messages
end
return M

View File

@ -6,11 +6,24 @@ local fs = require 'fs'
package.path = package.path .. ';' .. hilbish.dataDir .. '/?/init.lua'
.. ';' .. hilbish.dataDir .. '/?/?.lua' .. ";" .. hilbish.dataDir .. '/?.lua'
hilbish.module.paths = '?.so;?/?.so;'
.. hilbish.userDir.data .. 'hilbish/libs/?/?.so'
.. ";" .. hilbish.userDir.data .. 'hilbish/libs/?.so'
table.insert(package.searchers, function(module)
local path = package.searchpath(module, hilbish.module.paths)
if not path then return nil end
-- it didnt work normally, idk
return function() return hilbish.module.load(path) end, path
end)
require 'nature.commands'
require 'nature.completions'
require 'nature.opts'
require 'nature.vim'
require 'nature.runner'
require 'nature.hummingbird'
local shlvl = tonumber(os.getenv 'SHLVL')
if shlvl ~= nil then

59
nature/object.lua 100644
View File

@ -0,0 +1,59 @@
---@class nature.object
---@field super nature.object
local Object = {}
Object.__index = Object
---Can be overrided by child objects to implement a constructor.
function Object:new() end
---@return nature.object
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
---Check if the object is strictly of the given type.
---@param T any
---@return boolean
function Object:is(T)
return getmetatable(self) == T
end
---Check if the object inherits from the given type.
---@param T any
---@return boolean
function Object:extends(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
---Metamethod to get a string representation of an object.
---@return string
function Object:__tostring()
return "Object"
end
---Methamethod to allow using the object call as a constructor.
---@return nature.object
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
return Object

View File

@ -16,7 +16,7 @@ setmetatable(hilbish.opts, {
local function setupOpt(name, default)
opts[name] = default
require('nature.opts.' .. name)
pcall(require, 'nature.opts.' .. name)
end
local defaultOpts = {
@ -25,7 +25,9 @@ local defaultOpts = {
greeting = string.format([[Welcome to {magenta}Hilbish{reset}, {cyan}%s{reset}.
The nice lil shell for {blue}Lua{reset} fanatics!
]], hilbish.user),
motd = true
motd = true,
fuzzy = false,
notifyJobFinish = true
}
for optsName, default in pairs(defaultOpts) do

View File

@ -0,0 +1,23 @@
local bait = require 'bait'
local lunacolors = require 'lunacolors'
bait.catch('job.done', function(job)
if not hilbish.opts.notifyJobFinish then return end
local notifText = string.format(lunacolors.format [[
Background job with ID#%d has exited (PID %d).
Command string: {bold}{yellow}%s{reset}]], job.id, job.pid, job.cmd)
if job.stdout ~= '' then
notifText = notifText .. '\n\nStandard output:\n' .. job.stdout
end
if job.stderr ~= '' then
notifText = notifText .. '\n\nStandard error:\n' .. job.stderr
end
hilbish.messages.send {
channel = 'jobNotify',
title = string.format('Job ID#%d Exited', job.id),
summary = string.format(lunacolors.format 'Background job with command {bold}{yellow}%s{reset} has finished running!', job.cmd),
text = notifText
}
end)

View File

@ -1,5 +1,7 @@
package readline
import "os"
// Character codes
const (
charCtrlA = iota + 1
@ -134,3 +136,57 @@ const (
const (
seqCtermFg255 = "\033[48;5;255m"
)
// TODO: return whether its actually a sequence or not
// remedies the edge case of someone literally typing Ctrl-A for example.
func (rl *Instance) ReadChar() string {
b := make([]byte, 1024)
i, _ := os.Stdin.Read(b)
r := []rune(string(b))
s := string(r[:i])
switch b[0] {
case charCtrlA: return "Ctrl-A"
case charCtrlB: return "Ctrl-B"
case charCtrlC: return "Ctrl-C"
case charEOF: return "Ctrl-D"
case charCtrlE: return "Ctrl-E"
case charCtrlF: return "Ctrl-F"
case charCtrlG: return "Ctrl-G"
case charBackspace, charBackspace2: return "Backspace"
case charTab: return "Tab"
case charCtrlK: return "Ctrl-K"
case charCtrlL: return "Ctrl-L"
case charCtrlN: return "Ctrl-N"
case charCtrlO: return "Ctrl-O"
case charCtrlP: return "Ctrl-P"
case charCtrlQ: return "Ctrl-Q"
case charCtrlR: return "Ctrl-R"
case charCtrlS: return "Ctrl-S"
case charCtrlT: return "Ctrl-T"
case charCtrlU: return "Ctrl-U"
case charCtrlV: return "Ctrl-V"
case charCtrlW: return "Ctrl-W"
case charCtrlX: return "Ctrl-X"
case charCtrlY: return "Ctrl-Y"
case charCtrlZ: return "Ctrl-Z"
case '\r': fallthrough
case '\n': return "Enter"
case charEscape:
switch s {
case string(charEscape): return "Escape"
case seqUp: return "Up"
case seqDown: return "Down"
case seqBackwards: return "Left"
case seqForwards: return "Right"
case seqCtrlLeftArrow: return "Ctrl-Left"
case seqCtrlRightArrow: return "Ctrl-Right"
case seqCtrlDelete, seqCtrlDelete2: return "Ctrl-Delete"
case seqHome, seqHomeSc: return "Home"
case seqEnd, seqEndSc: return "End"
case seqDelete, seqDelete2: return "Delete"
}
}
return s
}

View File

@ -71,10 +71,9 @@ func (g *CompletionGroup) init(rl *Instance) {
// The rx parameter is passed, as the shell already checked that the search pattern is valid.
func (g *CompletionGroup) updateTabFind(rl *Instance) {
suggs := make([]string, 0)
suggs := rl.Searcher(rl.search, g.Suggestions)
// We perform filter right here, so we create a new completion group, and populate it with our results.
for i := range g.Suggestions {
/*for i := range g.Suggestions {
if rl.regexSearch == nil { continue }
if rl.regexSearch.MatchString(g.Suggestions[i]) {
suggs = append(suggs, g.Suggestions[i])
@ -82,7 +81,7 @@ func (g *CompletionGroup) updateTabFind(rl *Instance) {
// this is a list so lets also check the descriptions
suggs = append(suggs, g.Suggestions[i])
}
}
}*/
// We overwrite the group's items, (will be refreshed as soon as something is typed in the search)
g.Suggestions = suggs

View File

@ -121,6 +121,7 @@ func (g *CompletionGroup) writeMap(rl *Instance) (comp string) {
}
description = g.Descriptions[g.Suggestions[i]]
rl.lineCompDescription = description
if len(description) > maxLength {
description = description[:maxLength-3] + "..."
}

View File

@ -167,8 +167,8 @@ func (rl *Instance) walkHistory(i int) {
rl.updateHelpers()
// In order to avoid having to type j/k twice each time for history navigation,
// we walk once again. This only ever happens when we aren't out of bounds.
if dedup && old == new {
// we walk once again. This only ever happens when we aren't out of bounds and the last history item was not a empty string.
if new != "" && dedup && old == new {
rl.walkHistory(i)
}
}

View File

@ -101,6 +101,7 @@ type Instance struct {
// Candidate / virtual completion string / etc
currentComp []rune // The currently selected item, not yet a real part of the input line.
lineComp []rune // Same as rl.line, but with the currentComp inserted.
lineCompDescription string
lineRemain []rune // When we complete in the middle of a line, we cut and keep the remain.
compAddSpace bool // If true, any candidate inserted into the real line is done with an added space.
@ -112,8 +113,10 @@ type Instance struct {
modeAutoFind bool // for when invoked via ^R or ^F outside of [tab]
searchMode FindMode // Used for varying hints, and underlying functions called
regexSearch *regexp.Regexp // Holds the current search regex match
search string
mainHist bool // Which history stdin do we want
histInfo []rune // We store a piece of hist info, for dual history sources
Searcher func(string, []string) []string
//
// History -----------------------------------------------------------------------------------
@ -229,6 +232,25 @@ func NewInstance() *Instance {
rl.HintFormatting = "\x1b[2m"
rl.evtKeyPress = make(map[string]func(string, []rune, int) *EventReturn)
rl.TempDirectory = os.TempDir()
rl.Searcher = func(needle string, haystack []string) []string {
suggs := make([]string, 0)
var err error
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
if err != nil {
rl.RefreshPromptLog(err.Error())
rl.infoText = []rune(Red("Failed to match search regexp"))
}
for _, hay := range haystack {
if rl.regexSearch == nil { continue }
if rl.regexSearch.MatchString(hay) {
suggs = append(suggs, hay)
}
}
return suggs
}
// Registers
rl.initRegisters()

View File

@ -6,7 +6,10 @@ import (
"fmt"
"os"
"regexp"
"strconv"
"syscall"
ansi "github.com/acarl005/stripansi"
)
var rxMultiline = regexp.MustCompile(`[\r\n]+`)
@ -838,10 +841,12 @@ func (rl *Instance) escapeSeq(r []rune) {
case seqCtrlDelete, seqCtrlDelete2, seqAltD:
if rl.searchMode == HistoryFind {
fmt.Println("\nCURRENT COMP\n", string(rl.lineComp), "\n\n\n")
rl.resetVirtualComp(true)
idx, err := strconv.Atoi(ansi.Strip(rl.lineCompDescription))
fmt.Println("\nCURRENT COMP\n", string(rl.lineComp), idx, "\n\n\n")
fmt.Println(idx, err)
rl.updateVirtualComp()
rl.renderHelpers()
rl.updateHelpers()
rl.viUndoSkipAppend = true
return
}

View File

@ -94,7 +94,7 @@ func (rl *Instance) getTabSearchCompletion() {
rl.getCurrentGroup()
// Set the info for this completion mode
rl.infoText = append([]rune("Completion search: "), rl.tfLine...)
rl.infoText = append([]rune("Completion search: " + UNDERLINE + BOLD), rl.tfLine...)
for _, g := range rl.tcGroups {
g.updateTabFind(rl)
@ -102,7 +102,7 @@ func (rl *Instance) getTabSearchCompletion() {
// If total number of matches is zero, we directly change the info, and return
if comps, _, _ := rl.getCompletionCount(); comps == 0 {
rl.infoText = append(rl.infoText, []rune(DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
rl.infoText = append(rl.infoText, []rune(RESET+DIM+RED+" ! no matches (Ctrl-G/Esc to cancel)"+RESET)...)
}
}

View File

@ -1,9 +1,5 @@
package readline
import (
"regexp"
)
// FindMode defines how the autocomplete suggestions display
type FindMode int
@ -30,12 +26,7 @@ func (rl *Instance) updateTabFind(r []rune) {
rl.tfLine = append(rl.tfLine, r...)
// The search regex is common to all search modes
var err error
rl.regexSearch, err = regexp.Compile("(?i)" + string(rl.tfLine))
if err != nil {
rl.RefreshPromptLog(err.Error())
rl.infoText = []rune(Red("Failed to match search regexp"))
}
rl.search = string(rl.tfLine)
// We update and print
rl.clearHelpers()

View File

@ -14,6 +14,7 @@ var (
// effects
BOLD = "\033[1m"
DIM = "\033[2m"
UNDERLINE = "\033[4m"
RESET = "\033[0m"
// colors
RED = "\033[31m"

21
rl.go
View File

@ -7,8 +7,9 @@ import (
"hilbish/util"
"github.com/maxlandon/readline"
rt "github.com/arnodel/golua/runtime"
"github.com/maxlandon/readline"
"github.com/sahilm/fuzzy"
)
type lineReader struct {
@ -24,6 +25,24 @@ func newLineReader(prompt string, noHist bool) *lineReader {
rl: rl,
}
regexSearcher := rl.Searcher
rl.Searcher = func(needle string, haystack []string) []string {
fz, _ := util.DoString(l, "return hilbish.opts.fuzzy")
fuzz, ok := fz.TryBool()
if !fuzz || !ok {
return regexSearcher(needle, haystack)
}
matches := fuzzy.Find(needle, haystack)
suggs := make([]string, 0)
for _, match := range matches {
suggs = append(suggs, match.Str)
}
return suggs
}
// we don't mind hilbish.read rl instances having completion,
// but it cant have shared history
if !noHist {

140
sink.go
View File

@ -1,8 +1,11 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"hilbish/util"
@ -15,9 +18,11 @@ var sinkMetaKey = rt.StringValue("hshsink")
// A sink is a structure that has input and/or output to/from
// a desination.
type sink struct{
writer io.Writer
reader io.Reader
writer *bufio.Writer
reader *bufio.Reader
file *os.File
ud *rt.UserData
autoFlush bool
}
func setupSinkType(rtm *rt.Runtime) {
@ -25,15 +30,36 @@ func setupSinkType(rtm *rt.Runtime) {
sinkMethods := rt.NewTable()
sinkFuncs := map[string]util.LuaExport{
"flush": {luaSinkFlush, 1, false},
"read": {luaSinkRead, 1, false},
"readAll": {luaSinkReadAll, 1, false},
"autoFlush": {luaSinkAutoFlush, 2, false},
"write": {luaSinkWrite, 2, false},
"writeln": {luaSinkWriteln, 2, false},
}
util.SetExports(l, sinkMethods, sinkFuncs)
sinkIndex := func(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
s, _ := sinkArg(c, 0)
arg := c.Arg(1)
val := sinkMethods.Get(arg)
if val != rt.NilValue {
return c.PushingNext1(t.Runtime, val), nil
}
keyStr, _ := arg.TryString()
switch keyStr {
case "pipe":
val = rt.BoolValue(false)
if s.file != nil {
fileInfo, _ := s.file.Stat();
val = rt.BoolValue(fileInfo.Mode() & os.ModeCharDevice == 0)
}
}
return c.PushingNext1(t.Runtime, val), nil
}
@ -41,6 +67,57 @@ func setupSinkType(rtm *rt.Runtime) {
l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
}
// #member
// readAll() -> string
// --- @returns string
// Reads all input from the sink.
func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
s, err := sinkArg(c, 0)
if err != nil {
return nil, err
}
lines := []string{}
for {
line, err := s.reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
lines = append(lines, line)
}
return c.PushingNext1(t.Runtime, rt.StringValue(strings.Join(lines, ""))), nil
}
// #member
// read() -> string
// --- @returns string
// Reads a liine of input from the sink.
func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
s, err := sinkArg(c, 0)
if err != nil {
return nil, err
}
str, _ := s.reader.ReadString('\n')
return c.PushingNext1(t.Runtime, rt.StringValue(str)), nil
}
// #member
// write(str)
// Writes data to a sink.
@ -59,6 +136,9 @@ func luaSinkWrite(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
s.writer.Write([]byte(data))
if s.autoFlush {
s.writer.Flush()
}
return c.Next(), nil
}
@ -81,22 +161,74 @@ func luaSinkWriteln(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
}
s.writer.Write([]byte(data + "\n"))
if s.autoFlush {
s.writer.Flush()
}
return c.Next(), nil
}
// #member
// flush()
// Flush writes all buffered input to the sink.
func luaSinkFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
if err := c.Check1Arg(); err != nil {
return nil, err
}
s, err := sinkArg(c, 0)
if err != nil {
return nil, err
}
s.writer.Flush()
return c.Next(), nil
}
// #member
// autoFlush(auto)
// Sets/toggles the option of automatically flushing output.
// A call with no argument will toggle the value.
// --- @param auto boolean|nil
func luaSinkAutoFlush(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
s, err := sinkArg(c, 0)
if err != nil {
return nil, err
}
v := c.Arg(1)
if v.Type() != rt.BoolType && v.Type() != rt.NilType {
return nil, fmt.Errorf("#1 must be a boolean")
}
value := !s.autoFlush
if v.Type() == rt.BoolType {
value = v.AsBool()
}
s.autoFlush = value
return c.Next(), nil
}
func newSinkInput(r io.Reader) *sink {
s := &sink{
reader: r,
reader: bufio.NewReader(r),
}
s.ud = sinkUserData(s)
if f, ok := r.(*os.File); ok {
s.file = f
}
return s
}
func newSinkOutput(w io.Writer) *sink {
s := &sink{
writer: w,
writer: bufio.NewWriter(w),
autoFlush: true,
}
s.ud = sinkUserData(s)

View File

@ -0,0 +1,9 @@
package main
import (
rt "github.com/arnodel/golua/runtime"
)
func Loader(rtm *rt.Runtime) rt.Value {
return rt.StringValue("hello world!")
}

Binary file not shown.

View File

@ -26,13 +26,14 @@ func SetFieldProtected(module, realModule *rt.Table, field string, value rt.Valu
}
// DoString runs the code string in the Lua runtime.
func DoString(rtm *rt.Runtime, code string) error {
func DoString(rtm *rt.Runtime, code string) (rt.Value, error) {
chunk, err := rtm.CompileAndLoadLuaChunk("<string>", []byte(code), rt.TableValue(rtm.GlobalEnv()))
var ret rt.Value
if chunk != nil {
_, err = rt.Call1(rtm.MainThread(), rt.FunctionValue(chunk))
ret, err = rt.Call1(rtm.MainThread(), rt.FunctionValue(chunk))
}
return err
return ret, err
}
// DoFile runs the contents of the file in the Lua runtime.

View File

@ -13,6 +13,7 @@ var (
var (
ver = "v2.2.0"
releaseName = "Poppy"
gitCommit string
gitBranch string
)

View File

@ -8,11 +8,11 @@ description: 'Something Unique. Hilbish is the new interactive shell for Lua fan
<div class="text-center">
<h1 class="fw-light">Something Unique.</h1>
<p>
<strong>Hilbish</strong> is the new interactive shell for Lua fans.<br>
Extensible, scriptable, configurable: All in Lua.
<strong>🌺 Hilbish</strong> is the new Moon-powered interactive shell for Lua fans!<br>
Extensible, scriptable, configurable: All in Lua.
</p>
<a href="install" class="btn btn-primary">Install</a>
<a href="https://github.com/Rosettea/Hilbish" class="btn btn-secondary" target="_blank">Github</a>
<a href="https://github.com/Rosettea/Hilbish" class="btn btn-secondary" target="_blank">GitHub</a>
</div>
<hr>
@ -108,14 +108,28 @@ description: 'Something Unique. Hilbish is the new interactive shell for Lua fan
<hr>
<h1 class="fw-light">Screenshots</h1>
<div class="row row-cols-1 row-cols-md-2 g-4">
<div class="col">
<img src="/Hilbish/pillprompt.png">
</div>
<div class="col">
<img src="/Hilbish/default.png">
</div>
<div class="col">
<img src="/Hilbish/tab.png">
</div>
</div>
<br>
<h1 class="fw-light">Why not just Lua?</h1>
<p>
Hilbish is your interactive shell as well as a just a Lua interpreter
and enhanced REPL.<br>
</p>
<ul class="list-group" style="max-width: 64em;">
<ul class="list-group">
<li class="list-group-item"><i class="fa-solid fa-battery-full"></i> Batteries included Lua runtime that's also your user shell!</li>
<li class="list-group-item"><i class="fa-solid fa-network-wired"></i> Hilbish is easily cross platform. It has OS agnostic interfaces for easy cross platform Lua code.</li>
<li class="list-group-item"><i class="fa-solid fa-network-wired"></i> Provides cross-platform and OS agnostic APIs to ensure your Lua code works everywhere Hilbish does, as expected.</li>
</ul>
<hr>
@ -123,11 +137,10 @@ description: 'Something Unique. Hilbish is the new interactive shell for Lua fan
<h1 class="fw-light">Try It Today!</h1>
<p>
Hilbish is known to run on the 3 major platforms (Windows, MacOS, Linux)
but likely builds on other Unixes!
<br>
Windows doesn't work as well as it should, so if you're a Windows user,
but likely builds on other Unixes! Windows doesn't work as well as it should,
so if you're a Windows user,
<a href="https://github.com/Rosettea/Hilbish/discussions/165">say something</a>!
<ul class="list-group" style="max-width: 64em;">
<ul class="list-group">
<li class="list-group-item"><i class="fa-solid fa-cloud-arrow-down"></i> <a href="/Hilbish/install" style="text-decoration: none;"><strong>Download</strong></a> the binary</li>
<li class="list-group-item"><i class="fa-solid fa-screwdriver-wrench"></i> <a href="https://github.com/Rosettea/Hilbish#manual-build" style="text-decoration: none;"><strong>Build</strong></a> from source</li>
</ul>

View File

@ -0,0 +1,66 @@
---
title: "Improving Hilbish's Branding"
date: 2023-04-13T22:15:31-04:00
draft: false
---
Happy birthday Hilbish! As of last month, Hilbish is now 2 years old.
Unfortunately I missed the official date, but I will still make a more
focused post on the date (19st).
I decided to fix up this website and Hilbish's logo, so that can
be thought of as something for the 2 years milestone?
# Logo
Hilbish's old logo was.. not that good. It definitely functioned
as a logo, but the yellow part of it looked ugly (sorry old logo).
<img src="https://safe.kashima.moe/4c6e9q484pcy.png" width=256>
<br>
You would have definitely seen the new logo, since it is currently
in use on the navigation bar and footer. Here it is in a bigger view:
<img src="https://safe.kashima.moe/oy72vpev2yi4.png" width=256>
<br>
# Website
Ever since this website was first made, from the release of v2.0, it has
been doing it's job of being a website good enough, but there were a few issues.
# Padding
Padding is very important! The edges of your screen need space to do nothing,
after all. On mobile or screens small enough, there would not be enough space
for the auto margin to fill, and since there was no padding besides that,
it means things would look a bit cramped. This was simple to fix.
Here it is before:
![Before](https://safe.kashima.moe/nupzzalt2oa4.png)
and after:
![After](https://safe.kashima.moe/r0ox4nazfi0q.png)
# Docs Navigation
On the docs page, the pages are on the left on desktop. Since
phones are too small to have this content on the side, it stays at the top.
This is a bit counter intuitive since it brings in extra scrolling
when navigating to every page for docs and just doesn't look that good.
A few months ago I made it collapse with the site wide navigation, but it
was not hidden by default. So a few improvements were made:
- Make the doc navigation hidden by default on mobile, just like site wide navigation
- Make doc navigation have the same look as site wide navigation
Here's a before:
![](https://safe.kashima.moe/krn0a6qwegdj.png)
and after:
![](https://safe.kashima.moe/sk11ighz47yb.png)
Looks a lot better now.
# Other Changes
If you haven't noticed, I have made other changes to the website.
This includes:
- Borders! Something this simple makes the website look a lot better, especially on mobile.
- More padding and margin everywhere. Home, doc pages, blog post listing.

View File

@ -0,0 +1,38 @@
---
title: "v2.1.1 Release"
date: 2023-04-01T18:15:42-04:00
draft: false
---
> The release with full changelogs and prebuilt binaries can be
seen at the [v2.1.1](https://github.com/Rosettea/Hilbish/releases/tag/v2.1.1)
tag.
Welcome to a fresh new release of Hilbish! Some people (or none) may be awaiting
the long coming v2.2 release with lots of features, but I *needed* to push
out this little bug fix (wink) release.
# Bug Fixes
## Validation checks for command input
When running this version, you may have noticed an odd message that sometimes
comes up when running commands. This is from the new TMOLI42SH
(The Meaning of Life is 42 String Hash) input validation scheme.
## Improved runtime code
Commands now have a chance of taking exactly 2-3s ~~more~~ less time of running due to
improvements in the code for shell runners!!!!!
## Validate lua code
Hilbish already threw an error when Lua code was not valid in syntax, but there was the
need for an extra validation scheme (called OpTTCLC - Opinion based Turing Test to Check Lua Code)
which results in less time wasted running invalid and TERRIBLE Lua code.
# Features
There is only 1 new feature in this glorious release.
## Fix your mistakes for the future
If you run a command that does not exist, Hilbish will say goodbye.
# Closing
Hope you enjoy this new release! It took a lot of effort to create this new version
while I was busy doing completely nothing. :)))

View File

@ -0,0 +1,15 @@
---
title: "v2.1.2 Release"
date: 2023-04-10T12:27:41-04:00
draft: false
---
> The release with full changelogs and prebuilt binaries can be
seen at the [v2.1.2](https://github.com/Rosettea/Hilbish/releases/tag/v2.1.2)
tag.
This release reverts the April Fool's code additions in v2.1.1. It is
functionally equal to v2.1.0. Nice!
A real release will come possibly in a few days or next week, so stay tuned for
the good and feature-filled release of v2.2!

View File

@ -5,7 +5,7 @@ weight: -40
menu: docs
---
Hilbish has a wide range of features to enhance the user's experience and
is always adding new ones. If there is something missing here or something
you would like to see, please [start a discussion](https://github.com/Rosettea/Hilbish/discussions)
Hilbish has a wide range of features to enhance the user's experience
new ones are always being added. If there is something missing here or
something you would like to see, please [start a discussion](https://github.com/Rosettea/Hilbish/discussions)
or comment on any existing ones which match your request.

View File

@ -0,0 +1,39 @@
---
title: Notification
description: Get notified of shell actions.
layout: doc
menu:
docs:
parent: "Features"
---
Hilbish features a simple notification system which can be
used by other plugins and parts of the shell to notify the user
of various actions. This is used via the `hilbish.message` interface.
A `message` is defined as a table with the following properties:
- `icon`: A unicode/emoji icon for the notification.
- `title`: The title of the message
- `text`: Message text/body
- `channel`: The source of the message. This should be a
unique and easily readable text identifier.
- `summary`: A short summary of the notification and message.
If this is not present and you are using this to display messages,
you should take part of the `text` instead.
The `hilbish.message` interface provides the following functions:
- `send(message)`: Sends a message and emits the `hilbish.notification`
signal. DO NOT emit the `hilbish.notification` signal directly, or
the message will not be stored by the message handler.
- `read(idx)`: Marks message at `idx` as read.
- `delete(idx)`: Removes message at `idx`.
- `readAll()`: Marks all messages as read.
- `clear()`: Deletes all messages.
There are a few simple use cases of this notification/messaging system.
It could also be used as some "inter-shell" messaging system (???) but
is intended to display to users.
An example is notifying users of completed jobs/commands ran in the background.
Any Hilbish-native command (think the upcoming Greenhouse pager) can display
it.

View File

@ -13,5 +13,9 @@ is that it runs Lua first and then falls back to shell script.
In some cases, someone might want to switch to just shell script to avoid
it while interactive but still have a Lua config, or go full Lua to use
Hilbish as a REPL. This also allows users to add alternative languages,
instead of either like Fennel.
Hilbish as a REPL. This also allows users to add alternative languages like
Fennel as the interactive script runner.
Runner mode can also be used to handle specific kinds of input before
evaluating like normal, which is how [Link.hsh](https://github.com/TorchedSammy/Link.hsh)
handles links.

View File

@ -15,9 +15,10 @@ To exit, you can either run the `exit` command or hit Ctrl+D.
There are a few ways to make Hilbish your default shell. A simple way is
to make it your user/login shell.
{{< warning `It is not recommended to set Hilbish as your login shell. That is expected to be a
POSIX compliant shell, which Hilbish is not. At most, there will just be a
few variables missing in your environment` >}}
{{< warning `It is not recommended to set Hilbish as your login shell. That
is expected to be a POSIX compliant shell, which Hilbish is not. Though if
you still decide to do it, there will just be a few variables missing in
your environment` >}}
To do that, simply run `chsh -s /usr/bin/hilbish`.
Some distros (namely Fedora) might have `lchsh` instead, which is used like `lchsh <user>`.

View File

@ -15,7 +15,12 @@ have breaking changes.
For the latest **stable release**, check here: https://github.com/Rosettea/Hilbish/releases/latest
For a **development build**: https://nightly.link/Rosettea/Hilbish/workflows/build/master
## Compiling
To read the steps for compiling Hilbish, head over to the [GitHub repository.](https://github.com/Rosettea/Hilbish#build)
## Package Repositories
Methods of installing Hilbish for your Linux distro.
### Fedora (COPR)
An official COPR is offered to install Hilbish easily on Fedora.
Enable the repo:

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -1,31 +1,43 @@
{{ define "main" }}
<div class="container py-3 row">
<div class="container" style="width: 240px;">
<div class="p-3 col">
<ul class="nav nav-pills mb-auto-collapse" id="navbarSupportedContent">
{{ $currentPage := . }}
{{ range .Site.Menus.docs.ByWeight.Reverse }}
<li class="nav-item">
<a href="{{ .URL }}" class="nav-link">
<strong>{{ .Title }}</strong>
</a>
</li>
{{ if .Children }}
<ul style="list-style: none;">
{{ range .Children }}
<li class="nav-item">
<a href="{{ .URL }}" class="nav-link">
{{ .Title }}
</a>
</li>
<style>
@media (min-width: 768px) {
.sidenav {
width: 240px;
}
}
</style>
<div class="row">
<nav class="navbar-expand-md bg-light sidenav border-end">
<div class="container">
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<nav class="navbar navbar-expand-md bg-light sidenav col" style="margin-top: -20px;">
<ul class="navbar-nav me-auto mb-2 mb-lg-0 flex-column col mt-4">
{{ $currentPage := . }}
{{ range .Site.Menus.docs.ByWeight.Reverse }}
<li class="nav-item">
<a href="{{ .URL }}" class="nav-link">
<strong>{{ .Title }}</strong>
</a>
</li>
{{ if .Children }}
<ul style="list-style: none;">
{{ range .Children }}
<li class="nav-item">
<a href="{{ .URL }}" class="nav-link">
{{ .Title }}
</a>
</li>
{{ end }}
</ul>
{{ end }}
</ul>
{{ end }}
{{ end }}
</ul>
{{ end }}
</ul>
</nav>
</div>
</div>
</div>
<div class="p-3 col">
</nav>
<container class="col mt-2" style="padding-left: 20px; padding-right: 20px;">
<div>
<h1>{{ .Title }}</h1>
<p><em>
@ -47,7 +59,7 @@
Want to help improve this page? <a href="https://github.com/Rosettea/Hilbish/issues/new/choose">Create an issue.</a>
</p>
</div>
</div>
</container>
</div>
{{ end }}

View File

@ -1,5 +1,5 @@
{{ define "main" }}
<main>
<main class="mt-4 mb-4">
<div class="row row-cols-1 row-cols-md-1 g-4">
{{ range where .Site.RegularPages "Section" "in" "blog" }}
<div class="col d-flex justify-content-center">

View File

@ -1,6 +1,8 @@
{{ define "main" }}
<main style="max-width: 80em; margin: auto;">
{{.Content}}
</main>
<div>
<main style="padding-left: 20px; padding-right: 20px; padding-top: 15px; max-width: 84em; margin: auto;">
{{.Content}}
</main>
</div>
{{ end }}

View File

@ -1,24 +1,11 @@
<footer class="footer mt-auto mt-auto py-3 bg-light row">
<footer class="footer mt-auto mt-auto py-3 bg-light row border-top">
<div class="col mb-3">
</div>
<div class="col mb-3">
<a href="/Hilbish" class="d-flex align-items-center mb-3 link-dark text-decoration-none">
<img src="/Hilbish/hilbish-flower.png" alt="" height="48" class="d-inline-block align-text-top">
<img src="/Hilbish/hilbish-logo-and-text.png" alt="" height="48" class="d-inline-block align-text-top" style="margin-left: -20px">
</a>
<p class="text-muted">
Rosettea &copy; 2022
<br>
Made with <i class="fa-solid fa-heart" style="color: #f6345b;"></i>
</p>
</div>
<div class="col mb-3"></div>
<div class="col mb-3"></div>
<div class="col mb-3"></div>
<div class="col mb-3">
<h5>Hilbish</h5>
<ul class="nav flex-column">
<li class="nav-item mb-2"><a href="/Hilbish" class="nav-link p-0 text-muted">Home</a></li>
<li class="nav-item mb-2"><a href="/Hilbish/docs/faq" class="nav-link p-0 text-muted">FAQ</a></li>
@ -29,4 +16,16 @@
</div>
<div class="col mb-3"></div>
<div class="col mb-3"></div>
<div class="col mb-3"></div>
<div class="col mb-3">
<p class="text-muted mt-4">
Rosettea &copy; 2022-2023
<br>
Made with <i class="fa-solid fa-heart" style="color: #f6345b;"></i>
</p>
</div>
<div class="col mb-3"></div>
</footer>

View File

@ -1,9 +1,8 @@
<header>
<nav class="navbar navbar-expand-md sticky-top bg-light">
<nav class="navbar navbar-expand-md sticky-top bg-light border-bottom">
<div class="container-fluid">
<a class="navbar-brand" href="/Hilbish">
<img src="/Hilbish/hilbish-flower.png" alt="" height="24" class="d-inline-block align-text-top">
Hilbish
<img src="/Hilbish/hilbish-logo-and-text.png" alt="" height="48">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>