diff --git a/nature/commands/doc.lua b/nature/commands/doc.lua index d37e677..841f912 100644 --- a/nature/commands/doc.lua +++ b/nature/commands/doc.lua @@ -1,6 +1,8 @@ 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/' @@ -85,8 +87,9 @@ Available sections: ]] .. table.concat(modules, ', ') f:close() end + local gh = Greenhouse(sinks.out) local backtickOccurence = 0 - sinks.out:writeln(lunacolors.format(doc:gsub('`', function() + local page = Page(lunacolors.format(doc:gsub('`', function() backtickOccurence = backtickOccurence + 1 if backtickOccurence % 2 == 0 then return '{reset}' @@ -97,4 +100,6 @@ Available sections: ]] .. table.concat(modules, ', ') local signature = t:gsub('<.->(.-)', '{underline}%1'):gsub('\\', '<') return '{bold}{yellow}' .. signature .. '{reset}' end))) + gh:addPage(page) + gh:initUi() end) diff --git a/nature/commands/greenhouse.lua b/nature/commands/greenhouse.lua new file mode 100644 index 0000000..7b38d33 --- /dev/null +++ b/nature/commands/greenhouse.lua @@ -0,0 +1,28 @@ +local ansikit = require 'ansikit' +local bait = require 'bait' +local commander = require 'commander' +local hilbish = require 'hilbish' +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) + + if sinks['in'].pipe then + local page = Page(sinks['in']:readAll()) + gh:addPage(page) + end + + for _, name in ipairs(args) do + local f = io.open(name, 'r') + if not f then + sinks.err:writeln(string.format('could not open file %s', name)) + end + + local page = Page(f:read '*a') + gh:addPage(page) + end + + gh:initUi() +end) diff --git a/nature/greenhouse/init.lua b/nature/greenhouse/init.lua new file mode 100644 index 0000000..3cd4133 --- /dev/null +++ b/nature/greenhouse/init.lua @@ -0,0 +1,166 @@ +-- 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 terminal = require 'terminal' +local Object = require 'nature.object' + +local Greenhouse = Object:extend() + +function Greenhouse:new(sink) + local size = terminal.size() + self.region = size + self.start = 1 + self.offset = 1 + self.sink = sink + self.pages = {} + self.curPage = 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 + +function Greenhouse:draw() + local lines = self.pages[self.curPage].lines + self.sink:write(ansikit.getCSI(self.start .. ';1', 'H')) + self.sink:write(ansikit.getCSI(2, 'J')) + + -- the -2 negate is for the command and status line + for i = self.offset, self.offset + (self.region.height - self.start) - 2 do + if i > #lines then break end + self.sink:writeln('\r' .. lines[i]:gsub('\t', ' '):sub(0, self.region.width - 2)) + end + self.sink:write '\r' + + self.sink:write(ansikit.getCSI(self.region.height - self.start.. ';1', 'H')) + self.sink:writeln(string.format('\27[0mPage %d', self.curPage)) +end + +function Greenhouse:scroll(direction) + local lines = self.pages[self.curPage].lines + + local oldOffset = self.offset + if direction == 'down' then + self.offset = math.min(self.offset + 1, #lines) + 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() + local size = terminal.size() + self.region = size + + self:draw() +end + +function Greenhouse:next() + local oldCurrent = self.curPage + self.curPage = math.min(self.curPage + 1, #self.pages) + if self.curPage ~= oldCurrent then + self.offset = 1 + self:draw() + end +end + +function Greenhouse:previous() + local oldCurrent = self.curPage + self.curPage = math.max(self.curPage - 1, 1) + if self.curPage ~= oldCurrent then + self.offset = 1 + self:draw() + end +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() + if c == 3 then + done = true + 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 + print('\nchar:') + print(c) + + ::continue:: + end + end) + + while not done do + -- + end + ansikit.screenMain() +end + +function read() + terminal.saveState() + terminal.setRaw() + local c = io.read(1) + + terminal.restoreState() + return c:byte() +end + +return Greenhouse diff --git a/nature/greenhouse/page.lua b/nature/greenhouse/page.lua new file mode 100644 index 0000000..09f1e91 --- /dev/null +++ b/nature/greenhouse/page.lua @@ -0,0 +1,13 @@ +local Object = require 'nature.object' + +local Page = Object:extend() + +function Page:new(text) + self:setText(text) +end + +function Page:setText(text) + self.lines = string.split(text, '\n') +end + +return Page diff --git a/nature/object.lua b/nature/object.lua new file mode 100644 index 0000000..053be4a --- /dev/null +++ b/nature/object.lua @@ -0,0 +1,59 @@ +---@class nature.object +---@field super nature.object +local Object = {} +Object.__index = Object + +---Can be overrided by child objects to implement a constructor. +function Object:new() end + +---@return nature.object +function Object:extend() + local cls = {} + for k, v in pairs(self) do + if k:find("__") == 1 then + cls[k] = v + end + end + cls.__index = cls + cls.super = self + setmetatable(cls, self) + return cls +end + +---Check if the object is strictly of the given type. +---@param T any +---@return boolean +function Object:is(T) + return getmetatable(self) == T +end + +---Check if the object inherits from the given type. +---@param T any +---@return boolean +function Object:extends(T) + local mt = getmetatable(self) + while mt do + if mt == T then + return true + end + mt = getmetatable(mt) + end + return false +end + +---Metamethod to get a string representation of an object. +---@return string +function Object:__tostring() + return "Object" +end + +---Methamethod to allow using the object call as a constructor. +---@return nature.object +function Object:__call(...) + local obj = setmetatable({}, self) + obj:new(...) + return obj +end + + +return Object diff --git a/sink.go b/sink.go index 2ecc19d..3aa5507 100644 --- a/sink.go +++ b/sink.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "strings" "hilbish/util" @@ -31,6 +32,7 @@ func setupSinkType(rtm *rt.Runtime) { sinkFuncs := map[string]util.LuaExport{ "flush": {luaSinkFlush, 1, false}, "read": {luaSinkRead, 1, false}, + "readAll": {luaSinkReadAll, 1, false}, "autoFlush": {luaSinkAutoFlush, 2, false}, "write": {luaSinkWrite, 2, false}, "writeln": {luaSinkWriteln, 2, false}, @@ -65,10 +67,42 @@ func setupSinkType(rtm *rt.Runtime) { l.SetRegistry(sinkMetaKey, rt.TableValue(sinkMeta)) } + +// #member +// readAll() -> string +// --- @returns string +// Reads all input from the sink. +func luaSinkReadAll(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { + if err := c.Check1Arg(); err != nil { + return nil, err + } + + s, err := sinkArg(c, 0) + if err != nil { + return nil, err + } + + lines := []string{} + for { + line, err := s.reader.ReadString('\n') + if err != nil { + if err == io.EOF { + break + } + + return nil, err + } + + lines = append(lines, line) + } + + return c.PushingNext1(t.Runtime, rt.StringValue(strings.Join(lines, ""))), nil +} + // #member // read() -> string // --- @returns string -// Reads input from the sink. +// Reads a liine of input from the sink. func luaSinkRead(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) { if err := c.Check1Arg(); err != nil { return nil, err