mirror of https://github.com/Hilbis/Hilbish
chore: merge from master
commit
504e9b272a
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -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)
|
||||
|
|
44
README.md
44
README.md
|
@ -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.
|
||||
|
|
|
@ -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}}
|
||||
|
||||
|
|
27
aliases.go
27
aliases.go
|
@ -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
13
api.go
|
@ -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 |
|
@ -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)]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
11
editor.go
11
editor.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
3
go.mod
|
@ -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
20
go.sum
|
@ -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
2
lua.go
|
@ -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.")
|
||||
}
|
||||
|
|
8
main.go
8
main.go
|
@ -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:]...)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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
|
||||