mirror of https://github.com/Hilbis/Hilbish
Compare commits
1 Commits
df980f5fff
...
be1063ea0e
Author | SHA1 | Date |
---|---|---|
sammyette | be1063ea0e |
|
@ -8,7 +8,6 @@
|
||||||
- `pipe` property to check if a sink with input is a pipe (like stdin)
|
- `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`)
|
- Add fuzzy search to history search (enable via `hilbish.opts.fuzzy = true`)
|
||||||
- Show indexes on cdr list
|
- 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.messages` interface (details in [#219])
|
||||||
- `hilbish.notification` signal when a message/notification is sent
|
- `hilbish.notification` signal when a message/notification is sent
|
||||||
- `notifyJobFinish` opt to send a notification when background jobs are
|
- `notifyJobFinish` opt to send a notification when background jobs are
|
||||||
|
@ -16,10 +15,6 @@ completed.
|
||||||
- Allow numbered arg substitutions in aliases.
|
- Allow numbered arg substitutions in aliases.
|
||||||
- Example: `hilbish.alias('hello', 'echo %1 says hello')` allows the user to run `hello hilbish`
|
- Example: `hilbish.alias('hello', 'echo %1 says hello')` allows the user to run `hello hilbish`
|
||||||
which will output `hilbish says hello`.
|
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
|
### Fixed
|
||||||
- Return the prefix when calling `hilbish.completions.call`
|
- Return the prefix when calling `hilbish.completions.call`
|
||||||
|
|
|
@ -126,10 +126,7 @@ A call with no argument will toggle the value.
|
||||||
Flush writes all buffered input to the sink.
|
Flush writes all buffered input to the sink.
|
||||||
|
|
||||||
#### read() -> string
|
#### read() -> string
|
||||||
Reads a liine of input from the sink.
|
Reads input from the sink.
|
||||||
|
|
||||||
#### readAll() -> string
|
|
||||||
Reads all input from the sink.
|
|
||||||
|
|
||||||
#### write(str)
|
#### write(str)
|
||||||
Writes data to a sink.
|
Writes data to a sink.
|
||||||
|
|
|
@ -21,10 +21,6 @@ Returns the text that is at the register.
|
||||||
### insert(text)
|
### insert(text)
|
||||||
Inserts text into the line.
|
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)
|
### setVimRegister(register, text)
|
||||||
Sets the vim register at `register` to hold the passed text.
|
Sets the vim register at `register` to hold the passed text.
|
||||||
|
|
||||||
|
|
11
editor.go
11
editor.go
|
@ -16,7 +16,6 @@ func editorLoader(rtm *rt.Runtime) *rt.Table {
|
||||||
"setVimRegister": {editorSetRegister, 1, false},
|
"setVimRegister": {editorSetRegister, 1, false},
|
||||||
"getVimRegister": {editorGetRegister, 2, false},
|
"getVimRegister": {editorGetRegister, 2, false},
|
||||||
"getLine": {editorGetLine, 0, false},
|
"getLine": {editorGetLine, 0, false},
|
||||||
"readChar": {editorReadChar, 0, false},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod := rt.NewTable()
|
mod := rt.NewTable()
|
||||||
|
@ -95,13 +94,3 @@ func editorGetLine(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.StringValue(string(buf))), nil
|
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,10 +40,6 @@ function hilbish.editor.getVimRegister(register) end
|
||||||
--- Inserts text into the line.
|
--- Inserts text into the line.
|
||||||
function hilbish.editor.insert(text) end
|
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.
|
--- Sets the vim register at `register` to hold the passed text.
|
||||||
--- @param register string
|
--- @param register string
|
||||||
--- @param text string
|
--- @param text string
|
||||||
|
@ -200,14 +196,10 @@ function hilbish:autoFlush(auto) end
|
||||||
--- Flush writes all buffered input to the sink.
|
--- Flush writes all buffered input to the sink.
|
||||||
function hilbish:flush() end
|
function hilbish:flush() end
|
||||||
|
|
||||||
--- Reads a liine of input from the sink.
|
--- Reads input from the sink.
|
||||||
--- @returns string
|
--- @returns string
|
||||||
function hilbish:read() end
|
function hilbish:read() end
|
||||||
|
|
||||||
--- Reads all input from the sink.
|
|
||||||
--- @returns string
|
|
||||||
function hilbish:readAll() end
|
|
||||||
|
|
||||||
--- Writes data to a sink.
|
--- Writes data to a sink.
|
||||||
function hilbish:write(str) end
|
function hilbish:write(str) end
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
local ansikit = require 'ansikit'
|
|
||||||
local commander = require 'commander'
|
local commander = require 'commander'
|
||||||
local fs = require 'fs'
|
local fs = require 'fs'
|
||||||
local lunacolors = require 'lunacolors'
|
local lunacolors = require 'lunacolors'
|
||||||
local Greenhouse = require 'nature.greenhouse'
|
|
||||||
local Page = require 'nature.greenhouse.page'
|
|
||||||
|
|
||||||
commander.register('doc', function(args, sinks)
|
commander.register('doc', function(args, sinks)
|
||||||
local moddocPath = hilbish.dataDir .. '/docs/'
|
local moddocPath = hilbish.dataDir .. '/docs/'
|
||||||
|
@ -12,6 +9,11 @@ commander.register('doc', function(args, sinks)
|
||||||
-- hilbish git
|
-- hilbish git
|
||||||
moddocPath = './docs/'
|
moddocPath = './docs/'
|
||||||
end
|
end
|
||||||
|
local apidocHeader = [[
|
||||||
|
# %s
|
||||||
|
{grayBg} {white}{italic}%s {reset}
|
||||||
|
|
||||||
|
]]
|
||||||
|
|
||||||
local modules = table.map(fs.readdir(moddocPath), function(f)
|
local modules = table.map(fs.readdir(moddocPath), function(f)
|
||||||
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', '')))
|
return lunacolors.underline(lunacolors.blue(string.gsub(f, '.md', '')))
|
||||||
|
@ -23,15 +25,47 @@ to Hilbish.
|
||||||
|
|
||||||
Usage: doc <section> [subdoc]
|
Usage: doc <section> [subdoc]
|
||||||
Available sections: ]] .. table.concat(modules, ', ')
|
Available sections: ]] .. table.concat(modules, ', ')
|
||||||
local f
|
if #args > 0 then
|
||||||
local function handleYamlInfo(d)
|
local mod = args[1]
|
||||||
local vals = {}
|
|
||||||
local docs = d
|
|
||||||
|
|
||||||
local valsStr = docs:match '%-%-%-\n([^%-%-%-]+)\n'
|
local f = io.open(moddocPath .. mod .. '.md', 'rb')
|
||||||
print(valsStr)
|
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 vals = {}
|
||||||
if valsStr then
|
if valsStr then
|
||||||
docs = docs:sub(valsStr:len() + 10, #docs)
|
local _, endpos = funcdocs:find('---\n' .. valsStr .. '\n---\n\n', 1, true)
|
||||||
|
funcdocs = funcdocs:sub(endpos + 1, #funcdocs)
|
||||||
|
|
||||||
-- parse vals
|
-- parse vals
|
||||||
local lines = string.split(valsStr, '\n')
|
local lines = string.split(valsStr, '\n')
|
||||||
|
@ -44,113 +78,23 @@ Available sections: ]] .. table.concat(modules, ', ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if mod == 'api' then
|
||||||
--docs = docs:sub(1, #docs - 1)
|
funcdocs = string.format(apidocHeader, vals.title, vals.description or 'no description.') .. funcdocs
|
||||||
return docs, vals
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
||||||
|
doc = funcdocs:sub(1, #funcdocs - 1)
|
||||||
|
f:close()
|
||||||
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 backtickOccurence = 0
|
||||||
local function formatDocText(d)
|
sinks.out:writeln(lunacolors.format(doc:gsub('`', function()
|
||||||
return lunacolors.format(d:gsub('`', function()
|
backtickOccurence = backtickOccurence + 1
|
||||||
backtickOccurence = backtickOccurence + 1
|
if backtickOccurence % 2 == 0 then
|
||||||
if backtickOccurence % 2 == 0 then
|
return '{reset}'
|
||||||
return '{reset}'
|
else
|
||||||
else
|
return '{underline}{green}'
|
||||||
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
|
end
|
||||||
|
end):gsub('\n#+.-\n', function(t)
|
||||||
local f = io.open(moddocPath .. sdFile, 'rb')
|
local signature = t:gsub('<.->(.-)</.->', '{underline}%1'):gsub('\\', '<')
|
||||||
local doc, vals = handleYamlInfo(f:read '*a':gsub('-([%d]+)', '%1'))
|
return '{bold}{yellow}' .. signature .. '{reset}'
|
||||||
local page = Page(vals.title, formatDocText(doc))
|
end)))
|
||||||
page.description = vals.description
|
|
||||||
gh:addPage(page)
|
|
||||||
end
|
|
||||||
ansikit.hideCursor()
|
|
||||||
gh:initUi()
|
|
||||||
end)
|
end)
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
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)
|
|
|
@ -1,328 +0,0 @@
|
||||||
-- Greenhouse is a simple text scrolling handler for terminal programs.
|
|
||||||
-- The idea is that it can be set a region to do its scrolling and paging
|
|
||||||
-- job and then the user can draw whatever outside it.
|
|
||||||
-- This reduces code duplication for the message viewer
|
|
||||||
-- and flowerbook.
|
|
||||||
|
|
||||||
local ansikit = require 'ansikit'
|
|
||||||
local lunacolors = require 'lunacolors'
|
|
||||||
local terminal = require 'terminal'
|
|
||||||
local Page = require 'nature.greenhouse.page'
|
|
||||||
local Object = require 'nature.object'
|
|
||||||
|
|
||||||
local Greenhouse = Object:extend()
|
|
||||||
|
|
||||||
function Greenhouse:new(sink)
|
|
||||||
local size = terminal.size()
|
|
||||||
self.region = size
|
|
||||||
self.contents = nil -- or can be a table
|
|
||||||
self.start = 1 -- where to start drawing from (should replace with self.region.y)
|
|
||||||
self.offset = 1 -- vertical text offset
|
|
||||||
self.sink = sink
|
|
||||||
self.pages = {}
|
|
||||||
self.curPage = 1
|
|
||||||
self.keybinds = {
|
|
||||||
['Up'] = function(self) self:scroll 'up' end,
|
|
||||||
['Down'] = function(self) self:scroll 'down' end,
|
|
||||||
['Ctrl-Left'] = self.previous,
|
|
||||||
['Ctrl-Right'] = self.next,
|
|
||||||
['Ctrl-N'] = function(self) self:toc(true) end,
|
|
||||||
['Enter'] = function(self)
|
|
||||||
if self.isSpecial then
|
|
||||||
self:jump(self.specialPageIdx)
|
|
||||||
self:special(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
|
||||||
self.isSpecial = false
|
|
||||||
self.specialPage = nil
|
|
||||||
self.specialPageIdx = 1
|
|
||||||
self.specialOffset = 1
|
|
||||||
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:addPage(page)
|
|
||||||
table.insert(self.pages, page)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:updateCurrentPage(text)
|
|
||||||
local page = self.pages[self.curPage]
|
|
||||||
page:setText(text)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function sub(str, limit)
|
|
||||||
local overhead = 0
|
|
||||||
local function addOverhead(s)
|
|
||||||
overhead = overhead + string.len(s)
|
|
||||||
end
|
|
||||||
|
|
||||||
local s = str:gsub('\x1b%[%d+;%d+;%d+;%d+;%d+%w', addOverhead)
|
|
||||||
:gsub('\x1b%[%d+;%d+;%d+;%d+%w', addOverhead)
|
|
||||||
:gsub('\x1b%[%d+;%d+;%d+%w',addOverhead)
|
|
||||||
:gsub('\x1b%[%d+;%d+%w', addOverhead)
|
|
||||||
:gsub('\x1b%[%d+%w', addOverhead)
|
|
||||||
|
|
||||||
return s:sub(0, limit + overhead)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:draw()
|
|
||||||
local workingPage = self.pages[self.curPage]
|
|
||||||
local offset = self.offset
|
|
||||||
if self.isSpecial then
|
|
||||||
offset = self.specialOffset
|
|
||||||
workingPage = self.specialPage
|
|
||||||
end
|
|
||||||
|
|
||||||
if workingPage.lazy and not workingPage.loaded then
|
|
||||||
workingPage.initialize()
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines = workingPage.lines
|
|
||||||
self.sink:write(ansikit.getCSI(self.start .. ';1', 'H'))
|
|
||||||
self.sink:write(ansikit.getCSI(2, 'J'))
|
|
||||||
|
|
||||||
for i = offset, offset + self.region.height - 1 do
|
|
||||||
if i > #lines then break end
|
|
||||||
|
|
||||||
local writer = self.sink.writeln
|
|
||||||
if i == offset + self.region.height - 1 then writer = self.sink.write end
|
|
||||||
|
|
||||||
writer(self.sink, sub(lines[i]:gsub('\t', ' '), self.region.width))
|
|
||||||
end
|
|
||||||
self:render()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:render()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:scroll(direction)
|
|
||||||
if self.isSpecial then
|
|
||||||
if direction == 'down' then
|
|
||||||
self:next(true)
|
|
||||||
elseif direction == 'up' then
|
|
||||||
self:previous(true)
|
|
||||||
end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local lines = self.pages[self.curPage].lines
|
|
||||||
|
|
||||||
local oldOffset = self.offset
|
|
||||||
if direction == 'down' then
|
|
||||||
self.offset = math.min(self.offset + 1, math.max(1, #lines - self.region.height))
|
|
||||||
elseif direction == 'up' then
|
|
||||||
self.offset = math.max(self.offset - 1, 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
if self.offset ~= oldOffset then self:draw() end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:update()
|
|
||||||
self:resize()
|
|
||||||
if self.isSpecial then
|
|
||||||
self:updateSpecial()
|
|
||||||
end
|
|
||||||
|
|
||||||
self:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function Greenhouse:special(val)
|
|
||||||
self.isSpecial = val
|
|
||||||
self:update()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:toggleSpecial()
|
|
||||||
self:special(not self.isSpecial)
|
|
||||||
end
|
|
||||||
|
|
||||||
--- This function will be called when the special page
|
|
||||||
--- is on and needs to be updated.
|
|
||||||
function Greenhouse:updateSpecial()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:contents()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:toc(toggle)
|
|
||||||
if not self.isSpecial then
|
|
||||||
self.specialPageIdx = self.curPage
|
|
||||||
end
|
|
||||||
if toggle then self.isSpecial = not self.isSpecial end
|
|
||||||
-- Generate a special page for our table of contents
|
|
||||||
local tocText = string.format([[
|
|
||||||
%s
|
|
||||||
|
|
||||||
]], lunacolors.cyan(lunacolors.bold '―― Table of Contents ――'))
|
|
||||||
|
|
||||||
local genericPageCount = 1
|
|
||||||
local contents = self:contents()
|
|
||||||
if contents then
|
|
||||||
for i, c in ipairs(contents) do
|
|
||||||
local title = c.title
|
|
||||||
if c.active then
|
|
||||||
title = lunacolors.invert(title)
|
|
||||||
end
|
|
||||||
|
|
||||||
tocText = tocText .. title .. '\n'
|
|
||||||
end
|
|
||||||
else
|
|
||||||
for i, page in ipairs(self.pages) do
|
|
||||||
local title = page.title
|
|
||||||
if title == 'Page' then
|
|
||||||
title = 'Page #' .. genericPageCount
|
|
||||||
genericPageCount = genericPageCount + 1
|
|
||||||
end
|
|
||||||
if i == self.specialPageIdx then
|
|
||||||
title = lunacolors.invert(title)
|
|
||||||
end
|
|
||||||
|
|
||||||
tocText = tocText .. title .. '\n'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.specialPage = Page('TOC', tocText)
|
|
||||||
function self:updateSpecial()
|
|
||||||
self:toc()
|
|
||||||
end
|
|
||||||
self:draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:resize()
|
|
||||||
local size = terminal.size()
|
|
||||||
self.region = size
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:next(special)
|
|
||||||
local oldCurrent = special and self.specialPageIdx or self.curPage
|
|
||||||
local pageIdx = math.min(oldCurrent + 1, #self.pages)
|
|
||||||
|
|
||||||
if special then
|
|
||||||
self.specialPageIdx = pageIdx
|
|
||||||
else
|
|
||||||
self.curPage = pageIdx
|
|
||||||
end
|
|
||||||
|
|
||||||
if pageIdx ~= oldCurrent then
|
|
||||||
self.offset = 1
|
|
||||||
self:update()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:previous(special)
|
|
||||||
local oldCurrent = special and self.specialPageIdx or self.curPage
|
|
||||||
local pageIdx = math.max(self.curPage - 1, 1)
|
|
||||||
|
|
||||||
if special then
|
|
||||||
self.specialPageIdx = pageIdx
|
|
||||||
else
|
|
||||||
self.curPage = pageIdx
|
|
||||||
end
|
|
||||||
|
|
||||||
if pageIdx ~= oldCurrent then
|
|
||||||
self.offset = 1
|
|
||||||
self:update()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:jump(idx)
|
|
||||||
if idx ~= self.curPage then
|
|
||||||
self.offset = 1
|
|
||||||
end
|
|
||||||
self.curPage = idx
|
|
||||||
self:update()
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:keybind(key, callback)
|
|
||||||
self.keybinds[key] = callback
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:input(char)
|
|
||||||
end
|
|
||||||
|
|
||||||
function Greenhouse:initUi()
|
|
||||||
local ansikit = require 'ansikit'
|
|
||||||
local bait = require 'bait'
|
|
||||||
local commander = require 'commander'
|
|
||||||
local hilbish = require 'hilbish'
|
|
||||||
local terminal = require 'terminal'
|
|
||||||
local Page = require 'nature.greenhouse.page'
|
|
||||||
local done = false
|
|
||||||
|
|
||||||
bait.catch('signal.sigint', function()
|
|
||||||
ansikit.clear()
|
|
||||||
done = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
bait.catch('signal.resize', function()
|
|
||||||
self:update()
|
|
||||||
end)
|
|
||||||
|
|
||||||
ansikit.screenAlt()
|
|
||||||
ansikit.clear(true)
|
|
||||||
self:draw()
|
|
||||||
|
|
||||||
hilbish.goro(function()
|
|
||||||
while not done do
|
|
||||||
local c = read()
|
|
||||||
self:keybind('Ctrl-D', function()
|
|
||||||
done = true
|
|
||||||
end)
|
|
||||||
|
|
||||||
if self.keybinds[c] then
|
|
||||||
self.keybinds[c](self)
|
|
||||||
else
|
|
||||||
self:input(c)
|
|
||||||
end
|
|
||||||
|
|
||||||
--[[
|
|
||||||
if c == 27 then
|
|
||||||
local c1 = read()
|
|
||||||
if c1 == 91 then
|
|
||||||
local c2 = read()
|
|
||||||
if c2 == 66 then -- arrow down
|
|
||||||
self:scroll 'down'
|
|
||||||
elseif c2 == 65 then -- arrow up
|
|
||||||
self:scroll 'up'
|
|
||||||
end
|
|
||||||
|
|
||||||
if c2 == 49 then
|
|
||||||
local c3 = read()
|
|
||||||
if c3 == 59 then
|
|
||||||
local c4 = read()
|
|
||||||
if c4 == 53 then
|
|
||||||
local c5 = read()
|
|
||||||
if c5 == 67 then
|
|
||||||
self:next()
|
|
||||||
elseif c5 == 68 then
|
|
||||||
self:previous()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
]]--
|
|
||||||
|
|
||||||
::continue::
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
while not done do
|
|
||||||
--
|
|
||||||
end
|
|
||||||
ansikit.showCursor()
|
|
||||||
ansikit.screenMain()
|
|
||||||
end
|
|
||||||
|
|
||||||
function read()
|
|
||||||
terminal.saveState()
|
|
||||||
terminal.setRaw()
|
|
||||||
local c = hilbish.editor.readChar()
|
|
||||||
|
|
||||||
terminal.restoreState()
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
|
|
||||||
return Greenhouse
|
|
|
@ -1,32 +0,0 @@
|
||||||
local Object = require 'nature.object'
|
|
||||||
|
|
||||||
local Page = Object:extend()
|
|
||||||
|
|
||||||
function Page:new(title, text)
|
|
||||||
self:setText(text)
|
|
||||||
self.title = title or 'Page'
|
|
||||||
self.lazy = false
|
|
||||||
self.loaded = true
|
|
||||||
self.children = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
function Page:setText(text)
|
|
||||||
self.lines = string.split(text, '\n')
|
|
||||||
end
|
|
||||||
|
|
||||||
function Page:setTitle(title)
|
|
||||||
self.title = title
|
|
||||||
end
|
|
||||||
|
|
||||||
function Page:dynamic(initializer)
|
|
||||||
self.initializer = initializer
|
|
||||||
self.lazy = true
|
|
||||||
self.loaded = false
|
|
||||||
end
|
|
||||||
|
|
||||||
function Page:initialize()
|
|
||||||
self.initializer()
|
|
||||||
self.loaded = true
|
|
||||||
end
|
|
||||||
|
|
||||||
return Page
|
|
|
@ -1,59 +0,0 @@
|
||||||
---@class nature.object
|
|
||||||
---@field super nature.object
|
|
||||||
local Object = {}
|
|
||||||
Object.__index = Object
|
|
||||||
|
|
||||||
---Can be overrided by child objects to implement a constructor.
|
|
||||||
function Object:new() end
|
|
||||||
|
|
||||||
---@return nature.object
|
|
||||||
function Object:extend()
|
|
||||||
local cls = {}
|
|
||||||
for k, v in pairs(self) do
|
|
||||||
if k:find("__") == 1 then
|
|
||||||
cls[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
cls.__index = cls
|
|
||||||
cls.super = self
|
|
||||||
setmetatable(cls, self)
|
|
||||||
return cls
|
|
||||||
end
|
|
||||||
|
|
||||||
---Check if the object is strictly of the given type.
|
|
||||||
---@param T any
|
|
||||||
---@return boolean
|
|
||||||
function Object:is(T)
|
|
||||||
return getmetatable(self) == T
|
|
||||||
end
|
|
||||||
|
|
||||||
---Check if the object inherits from the given type.
|
|
||||||
---@param T any
|
|
||||||
---@return boolean
|
|
||||||
function Object:extends(T)
|
|
||||||
local mt = getmetatable(self)
|
|
||||||
while mt do
|
|
||||||
if mt == T then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
mt = getmetatable(mt)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
---Metamethod to get a string representation of an object.
|
|
||||||
---@return string
|
|
||||||
function Object:__tostring()
|
|
||||||
return "Object"
|
|
||||||
end
|
|
||||||
|
|
||||||
---Methamethod to allow using the object call as a constructor.
|
|
||||||
---@return nature.object
|
|
||||||
function Object:__call(...)
|
|
||||||
local obj = setmetatable({}, self)
|
|
||||||
obj:new(...)
|
|
||||||
return obj
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return Object
|
|
|
@ -1,7 +1,5 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
// Character codes
|
// Character codes
|
||||||
const (
|
const (
|
||||||
charCtrlA = iota + 1
|
charCtrlA = iota + 1
|
||||||
|
@ -136,57 +134,3 @@ const (
|
||||||
const (
|
const (
|
||||||
seqCtermFg255 = "\033[48;5;255m"
|
seqCtermFg255 = "\033[48;5;255m"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: return whether its actually a sequence or not
|
|
||||||
// remedies the edge case of someone literally typing Ctrl-A for example.
|
|
||||||
func (rl *Instance) ReadChar() string {
|
|
||||||
b := make([]byte, 1024)
|
|
||||||
i, _ := os.Stdin.Read(b)
|
|
||||||
r := []rune(string(b))
|
|
||||||
s := string(r[:i])
|
|
||||||
|
|
||||||
switch b[0] {
|
|
||||||
case charCtrlA: return "Ctrl-A"
|
|
||||||
case charCtrlB: return "Ctrl-B"
|
|
||||||
case charCtrlC: return "Ctrl-C"
|
|
||||||
case charEOF: return "Ctrl-D"
|
|
||||||
case charCtrlE: return "Ctrl-E"
|
|
||||||
case charCtrlF: return "Ctrl-F"
|
|
||||||
case charCtrlG: return "Ctrl-G"
|
|
||||||
case charBackspace, charBackspace2: return "Backspace"
|
|
||||||
case charTab: return "Tab"
|
|
||||||
case charCtrlK: return "Ctrl-K"
|
|
||||||
case charCtrlL: return "Ctrl-L"
|
|
||||||
case charCtrlN: return "Ctrl-N"
|
|
||||||
case charCtrlO: return "Ctrl-O"
|
|
||||||
case charCtrlP: return "Ctrl-P"
|
|
||||||
case charCtrlQ: return "Ctrl-Q"
|
|
||||||
case charCtrlR: return "Ctrl-R"
|
|
||||||
case charCtrlS: return "Ctrl-S"
|
|
||||||
case charCtrlT: return "Ctrl-T"
|
|
||||||
case charCtrlU: return "Ctrl-U"
|
|
||||||
case charCtrlV: return "Ctrl-V"
|
|
||||||
case charCtrlW: return "Ctrl-W"
|
|
||||||
case charCtrlX: return "Ctrl-X"
|
|
||||||
case charCtrlY: return "Ctrl-Y"
|
|
||||||
case charCtrlZ: return "Ctrl-Z"
|
|
||||||
case '\r': fallthrough
|
|
||||||
case '\n': return "Enter"
|
|
||||||
case charEscape:
|
|
||||||
switch s {
|
|
||||||
case string(charEscape): return "Escape"
|
|
||||||
case seqUp: return "Up"
|
|
||||||
case seqDown: return "Down"
|
|
||||||
case seqBackwards: return "Left"
|
|
||||||
case seqForwards: return "Right"
|
|
||||||
case seqCtrlLeftArrow: return "Ctrl-Left"
|
|
||||||
case seqCtrlRightArrow: return "Ctrl-Right"
|
|
||||||
case seqCtrlDelete, seqCtrlDelete2: return "Ctrl-Delete"
|
|
||||||
case seqHome, seqHomeSc: return "Home"
|
|
||||||
case seqEnd, seqEndSc: return "End"
|
|
||||||
case seqDelete, seqDelete2: return "Delete"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
36
sink.go
36
sink.go
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"hilbish/util"
|
"hilbish/util"
|
||||||
|
|
||||||
|
@ -32,7 +31,6 @@ func setupSinkType(rtm *rt.Runtime) {
|
||||||
sinkFuncs := map[string]util.LuaExport{
|
sinkFuncs := map[string]util.LuaExport{
|
||||||
"flush": {luaSinkFlush, 1, false},
|
"flush": {luaSinkFlush, 1, false},
|
||||||
"read": {luaSinkRead, 1, false},
|
"read": {luaSinkRead, 1, false},
|
||||||
"readAll": {luaSinkReadAll, 1, false},
|
|
||||||
"autoFlush": {luaSinkAutoFlush, 2, false},
|
"autoFlush": {luaSinkAutoFlush, 2, false},
|
||||||
"write": {luaSinkWrite, 2, false},
|
"write": {luaSinkWrite, 2, false},
|
||||||
"writeln": {luaSinkWriteln, 2, false},
|
"writeln": {luaSinkWriteln, 2, false},
|
||||||
|
@ -67,42 +65,10 @@ func setupSinkType(rtm *rt.Runtime) {
|
||||||
l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
|
l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// #member
|
|
||||||
// readAll() -> string
|
|
||||||
// --- @returns string
|
|
||||||
// Reads all input from the sink.
|
|
||||||
func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
|
||||||
if err := c.Check1Arg(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s, err := sinkArg(c, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := []string{}
|
|
||||||
for {
|
|
||||||
line, err := s.reader.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, line)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.PushingNext1(t.Runtime, rt.StringValue(strings.Join(lines, ""))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// #member
|
// #member
|
||||||
// read() -> string
|
// read() -> string
|
||||||
// --- @returns string
|
// --- @returns string
|
||||||
// Reads a liine of input from the sink.
|
// Reads input from the sink.
|
||||||
func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
|
||||||
if err := c.Check1Arg(); err != nil {
|
if err := c.Check1Arg(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Reference in New Issue