chore: merge from master

history-delete
sammyette 2023-11-11 20:44:22 -04:00
commit 504e9b272a
Signed by: sammyette
GPG Key ID: 904FC49417B44DCD
67 changed files with 1686 additions and 212 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