diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cdcf11..6a5250f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - `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 @@ -15,6 +16,10 @@ 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 ` + - Using this also brings enhancements to the `doc` command like easy + navigation of neighboring doc files. ### Fixed - Return the prefix when calling `hilbish.completions.call` diff --git a/docs/api/hilbish/_index.md b/docs/api/hilbish/_index.md index 4cf0180..a683172 100644 --- a/docs/api/hilbish/_index.md +++ b/docs/api/hilbish/_index.md @@ -126,7 +126,10 @@ A call with no argument will toggle the value. Flush writes all buffered input to the sink. #### read() -> string -Reads input from the sink. +Reads a liine of input from the sink. + +#### readAll() -> string +Reads all input from the sink. #### write(str) Writes data to a sink. diff --git a/docs/api/hilbish/hilbish.editor.md b/docs/api/hilbish/hilbish.editor.md index 30a3842..d75d4c2 100644 --- a/docs/api/hilbish/hilbish.editor.md +++ b/docs/api/hilbish/hilbish.editor.md @@ -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. diff --git a/editor.go b/editor.go index 3038f07..d720a41 100644 --- a/editor.go +++ b/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 +} diff --git a/emmyLuaDocs/hilbish.lua b/emmyLuaDocs/hilbish.lua index c26c7ec..9646b8e 100644 --- a/emmyLuaDocs/hilbish.lua +++ b/emmyLuaDocs/hilbish.lua @@ -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 @@ -196,10 +200,14 @@ function hilbish:autoFlush(auto) end --- Flush writes all buffered input to the sink. function hilbish:flush() end ---- Reads input from the sink. +--- 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 diff --git a/nature/commands/doc.lua b/nature/commands/doc.lua index d37e677..ee1e37c 100644 --- a/nature/commands/doc.lua +++ b/nature/commands/doc.lua @@ -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
[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//.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) diff --git a/nature/commands/greenhouse.lua b/nature/commands/greenhouse.lua new file mode 100644 index 0000000..9c155b0 --- /dev/null +++ b/nature/commands/greenhouse.lua @@ -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 = 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 ... + +example: greenhouse hello.md]] + return 1 + end + + ansikit.hideCursor() + gh:initUi() +end) diff --git a/nature/greenhouse/init.lua b/nature/greenhouse/init.lua new file mode 100644 index 0000000..d5877e8 --- /dev/null +++ b/nature/greenhouse/init.lua @@ -0,0 +1,328 @@ +-- Greenhouse is a simple text scrolling handler for terminal programs. +-- The idea is that it can be set a region to do its scrolling and paging +-- job and then the user can draw whatever outside it. +-- This reduces code duplication for the message viewer +-- and flowerbook. + +local ansikit = require 'ansikit' +local lunacolors = require 'lunacolors' +local terminal = require 'terminal' +local Page = require 'nature.greenhouse.page' +local Object = require 'nature.object' + +local Greenhouse = Object:extend() + +function Greenhouse:new(sink) + local size = terminal.size() + self.region = size + self.contents = nil -- or can be a table + self.start = 1 -- where to start drawing from (should replace with self.region.y) + self.offset = 1 -- vertical text offset + self.sink = sink + self.pages = {} + self.curPage = 1 + self.keybinds = { + ['Up'] = function(self) self:scroll 'up' end, + ['Down'] = function(self) self:scroll 'down' end, + ['Ctrl-Left'] = self.previous, + ['Ctrl-Right'] = self.next, + ['Ctrl-N'] = function(self) self:toc(true) end, + ['Enter'] = function(self) + if self.isSpecial then + self:jump(self.specialPageIdx) + self:special(false) + end + end + } + self.isSpecial = false + self.specialPage = nil + self.specialPageIdx = 1 + self.specialOffset = 1 + + return self +end + +function Greenhouse:addPage(page) + table.insert(self.pages, page) +end + +function Greenhouse:updateCurrentPage(text) + local page = self.pages[self.curPage] + page:setText(text) +end + +local function sub(str, limit) + local overhead = 0 + local function addOverhead(s) + overhead = overhead + string.len(s) + end + + local s = str:gsub('\x1b%[%d+;%d+;%d+;%d+;%d+%w', addOverhead) + :gsub('\x1b%[%d+;%d+;%d+;%d+%w', addOverhead) + :gsub('\x1b%[%d+;%d+;%d+%w',addOverhead) + :gsub('\x1b%[%d+;%d+%w', addOverhead) + :gsub('\x1b%[%d+%w', addOverhead) + + return s:sub(0, limit + overhead) +end + +function Greenhouse:draw() + local workingPage = self.pages[self.curPage] + local offset = self.offset + if self.isSpecial then + offset = self.specialOffset + workingPage = self.specialPage + end + + if workingPage.lazy and not workingPage.loaded then + workingPage.initialize() + end + + local lines = workingPage.lines + self.sink:write(ansikit.getCSI(self.start .. ';1', 'H')) + self.sink:write(ansikit.getCSI(2, 'J')) + + for i = offset, offset + self.region.height - 1 do + if i > #lines then break end + + local writer = self.sink.writeln + if i == offset + self.region.height - 1 then writer = self.sink.write end + + writer(self.sink, sub(lines[i]:gsub('\t', ' '), self.region.width)) + end + self:render() +end + +function Greenhouse:render() +end + +function Greenhouse:scroll(direction) + if self.isSpecial then + if direction == 'down' then + self:next(true) + elseif direction == 'up' then + self:previous(true) + end + return + end + + local lines = self.pages[self.curPage].lines + + local oldOffset = self.offset + if direction == 'down' then + self.offset = math.min(self.offset + 1, math.max(1, #lines - self.region.height)) + elseif direction == 'up' then + self.offset = math.max(self.offset - 1, 1) + end + + if self.offset ~= oldOffset then self:draw() end +end + +function Greenhouse:update() + self:resize() + if self.isSpecial then + self:updateSpecial() + end + + self:draw() +end + + +function Greenhouse:special(val) + self.isSpecial = val + self:update() +end + +function Greenhouse:toggleSpecial() + self:special(not self.isSpecial) +end + +--- This function will be called when the special page +--- is on and needs to be updated. +function Greenhouse:updateSpecial() +end + +function Greenhouse:contents() +end + +function Greenhouse:toc(toggle) + if not self.isSpecial then + self.specialPageIdx = self.curPage + end + if toggle then self.isSpecial = not self.isSpecial end + -- Generate a special page for our table of contents + local tocText = string.format([[ +%s + +]], lunacolors.cyan(lunacolors.bold '―― Table of Contents ――')) + + local genericPageCount = 1 + local contents = self:contents() + if contents then + for i, c in ipairs(contents) do + local title = c.title + if c.active then + title = lunacolors.invert(title) + end + + tocText = tocText .. title .. '\n' + end + else + for i, page in ipairs(self.pages) do + local title = page.title + if title == 'Page' then + title = 'Page #' .. genericPageCount + genericPageCount = genericPageCount + 1 + end + if i == self.specialPageIdx then + title = lunacolors.invert(title) + end + + tocText = tocText .. title .. '\n' + end + end + self.specialPage = Page('TOC', tocText) + function self:updateSpecial() + self:toc() + end + self:draw() +end + +function Greenhouse:resize() + local size = terminal.size() + self.region = size +end + +function Greenhouse:next(special) + local oldCurrent = special and self.specialPageIdx or self.curPage + local pageIdx = math.min(oldCurrent + 1, #self.pages) + + if special then + self.specialPageIdx = pageIdx + else + self.curPage = pageIdx + end + + if pageIdx ~= oldCurrent then + self.offset = 1 + self:update() + end +end + +function Greenhouse:previous(special) + local oldCurrent = special and self.specialPageIdx or self.curPage + local pageIdx = math.max(self.curPage - 1, 1) + + if special then + self.specialPageIdx = pageIdx + else + self.curPage = pageIdx + end + + if pageIdx ~= oldCurrent then + self.offset = 1 + self:update() + end +end + +function Greenhouse:jump(idx) + if idx ~= self.curPage then + self.offset = 1 + end + self.curPage = idx + self:update() +end + +function Greenhouse:keybind(key, callback) + self.keybinds[key] = callback +end + +function Greenhouse:input(char) +end + +function Greenhouse:initUi() + local ansikit = require 'ansikit' + local bait = require 'bait' + local commander = require 'commander' + local hilbish = require 'hilbish' + local terminal = require 'terminal' + local Page = require 'nature.greenhouse.page' + local done = false + + bait.catch('signal.sigint', function() + ansikit.clear() + done = true + end) + + bait.catch('signal.resize', function() + self:update() + end) + + ansikit.screenAlt() + ansikit.clear(true) + self:draw() + + hilbish.goro(function() + while not done do + local c = read() + self:keybind('Ctrl-D', function() + done = true + end) + + if self.keybinds[c] then + self.keybinds[c](self) + else + self:input(c) + end + + --[[ + if c == 27 then + local c1 = read() + if c1 == 91 then + local c2 = read() + if c2 == 66 then -- arrow down + self:scroll 'down' + elseif c2 == 65 then -- arrow up + self:scroll 'up' + end + + if c2 == 49 then + local c3 = read() + if c3 == 59 then + local c4 = read() + if c4 == 53 then + local c5 = read() + if c5 == 67 then + self:next() + elseif c5 == 68 then + self:previous() + end + end + end + end + end + goto continue + end + ]]-- + + ::continue:: + end + end) + + while not done do + -- + end + ansikit.showCursor() + ansikit.screenMain() +end + +function read() + terminal.saveState() + terminal.setRaw() + local c = hilbish.editor.readChar() + + terminal.restoreState() + return c +end + +return Greenhouse diff --git a/nature/greenhouse/page.lua b/nature/greenhouse/page.lua new file mode 100644 index 0000000..51d1440 --- /dev/null +++ b/nature/greenhouse/page.lua @@ -0,0 +1,32 @@ +local Object = require 'nature.object' + +local Page = Object:extend() + +function Page:new(title, text) + self:setText(text) + self.title = title or 'Page' + self.lazy = false + self.loaded = true + self.children = {} +end + +function Page:setText(text) + self.lines = string.split(text, '\n') +end + +function Page:setTitle(title) + self.title = title +end + +function Page:dynamic(initializer) + self.initializer = initializer + self.lazy = true + self.loaded = false +end + +function Page:initialize() + self.initializer() + self.loaded = true +end + +return Page 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/readline/codes.go b/readline/codes.go index 492bc72..dd8495d 100644 --- a/readline/codes.go +++ b/readline/codes.go @@ -1,5 +1,7 @@ package readline +import "os" + // Character codes const ( charCtrlA = iota + 1 @@ -134,3 +136,57 @@ const ( const ( seqCtermFg255 = "\033[48;5;255m" ) + +// TODO: return whether its actually a sequence or not +// remedies the edge case of someone literally typing Ctrl-A for example. +func (rl *Instance) ReadChar() string { + b := make([]byte, 1024) + i, _ := os.Stdin.Read(b) + r := []rune(string(b)) + s := string(r[:i]) + + switch b[0] { + case charCtrlA: return "Ctrl-A" + case charCtrlB: return "Ctrl-B" + case charCtrlC: return "Ctrl-C" + case charEOF: return "Ctrl-D" + case charCtrlE: return "Ctrl-E" + case charCtrlF: return "Ctrl-F" + case charCtrlG: return "Ctrl-G" + case charBackspace, charBackspace2: return "Backspace" + case charTab: return "Tab" + case charCtrlK: return "Ctrl-K" + case charCtrlL: return "Ctrl-L" + case charCtrlN: return "Ctrl-N" + case charCtrlO: return "Ctrl-O" + case charCtrlP: return "Ctrl-P" + case charCtrlQ: return "Ctrl-Q" + case charCtrlR: return "Ctrl-R" + case charCtrlS: return "Ctrl-S" + case charCtrlT: return "Ctrl-T" + case charCtrlU: return "Ctrl-U" + case charCtrlV: return "Ctrl-V" + case charCtrlW: return "Ctrl-W" + case charCtrlX: return "Ctrl-X" + case charCtrlY: return "Ctrl-Y" + case charCtrlZ: return "Ctrl-Z" + case '\r': fallthrough + case '\n': return "Enter" + case charEscape: + switch s { + case string(charEscape): return "Escape" + case seqUp: return "Up" + case seqDown: return "Down" + case seqBackwards: return "Left" + case seqForwards: return "Right" + case seqCtrlLeftArrow: return "Ctrl-Left" + case seqCtrlRightArrow: return "Ctrl-Right" + case seqCtrlDelete, seqCtrlDelete2: return "Ctrl-Delete" + case seqHome, seqHomeSc: return "Home" + case seqEnd, seqEndSc: return "End" + case seqDelete, seqDelete2: return "Delete" + } + } + + return s +} 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