From 9d2667d96477097658b911f81b33f4a0b98e292a Mon Sep 17 00:00:00 2001 From: wieselkatze Date: Tue, 4 Aug 2020 23:34:42 +0200 Subject: [PATCH 1/5] Fix textutils.pagedTabulate and tabulate Initial commit where textutils.pagedTabulate and textutils.tabulate should work as intended --- .../computercraft/lua/rom/apis/textutils.lua | 240 +++++++++++++++--- 1 file changed, 199 insertions(+), 41 deletions(-) diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index 0f191e522f..7839619284 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -152,61 +152,219 @@ function pagedPrint(_sText, _nFreeLines) return result end -local function tabulateCommon(bPaged, ...) - local tAll = table.pack(...) - for i = 1, tAll.n do - expect(i, tAll[i], "number", "table") - end - - local w, h = term.getSize() - local nMaxLen = w / 8 - for n, t in ipairs(tAll) do - if type(t) == "table" then - for nu, sItem in pairs(t) do - if type(sItem) ~= "string" then - error("bad argument #" .. n .. "." .. nu .. " (expected string, got " .. type(sItem) .. ")", 3) - end - nMaxLen = math.max(#sItem + 1, nMaxLen) +local function adjustColumnWidths(nWidth, tWidths) + --[[ + Tries to fit all columns in tWidths on the screen width (nWidth) + Shrinks biggest columns down to either + 1. Match the next smaller column + 2. by 1 if there are equally sized columns to alternate between the two + 3. By the amount the entire table is too wide if the column doesn't get too small + ]] + nWidth = nWidth - (#tWidths - 1) -- Spaces in between columns + + local nSum = 0 + -- Figure out the sum once; this well get adjusted accordingly later + -- and thus just needs to be done once + for _, v in pairs(tWidths) do + nSum = nSum + v + end + + while nSum > nWidth do + -- Next smaller elements holds element <= nMaxElement + -- This way we can prevent big elements being shrunk into oblivion if they are + -- way too large but there are other big elements + -- Both are only indices to the table + local nMaxElement, nNextSmallerElement = 1 + + for nKey, nColumnWidth in pairs(tWidths) do + if nColumnWidth > tWidths[nMaxElement] then + nNextSmallerElement = nMaxElement + nMaxElement = nKey + elseif nColumnWidth <= tWidths[nMaxElement] and (not nNextSmallerElement or nColumnWidth > tWidths[nNextSmallerElement]) then + nNextSmallerElement = nKey end end - end - local nCols = math.floor(w / nMaxLen) - local nLines = 0 - local function newLine() - if bPaged and nLines >= h - 3 then - pagedPrint() + + -- At this point we _will_ have a max element, but _may_ only have a next smaller element + local nShrinkAmount + if nNextSmallerElement then + if tWidths[nMaxElement] ~= tWidths[nNextSmallerElement] then + nShrinkAmount = math.min(nSum - nWidth, (tWidths[nMaxElement] - tWidths[nNextSmallerElement])) + else + -- This will alternate between the two elements + nShrinkAmount = 1 + end else - print() + -- This will only happen in a one element table where theres no + -- nNextSmallerElement <= nMaxElement + nShrinkAmount = nSum - nWidth + end + + local nNewWidth = tWidths[nMaxElement] - nShrinkAmount + -- Either the column contained text and would shrink to zero width + -- or it would shrink below zero + if nNewWidth < 1 and tWidths[nMaxElement] > 0 or nNewWidth < 0 then + error("Unable to fit table on screen width, column " .. nMaxElement .. " would shrink too much!", 2) + end + + tWidths[nMaxElement] = nNewWidth + nSum = nSum - nShrinkAmount -- Adjust the sum accordingly + end +end + +local function chopUpColumns(tWidths, tInput) + --[[ + This chops up all columns that would bee too wide to fit in the given space. + As this would be _very_ hard/messy to do whilst printing the table out, + it is done beforehand. + This will be done this way: + -> Columns that already fit will be left that way + -> Try to fit the maximum amount of words (non-space) on one line and wrap the rest into + the next line to be dealt with in next iteration + -> If word would still fit without whitespaces, those will be left out and the + rest wrapped + -> If word itself is longer than column width, it will get chopped up + and the rest of it wrapped into next line + ]] + + local function insertNewLine(bInserted, tRows, nRow, nColumn, sText) + if not bInserted then + -- Only execute this the first time (upon creation of new line) + table.insert(tRows, nRow, {[nColumn] = sText}) + return end - nLines = nLines + 1 + + tRows[nRow][nColumn] = sText end - local function drawCols(_t) - local nCol = 1 - for _, s in ipairs(_t) do - if nCol > nCols then - nCol = 1 - newLine() + for nRow, vElement in ipairs(tInput) do + if type(vElement) == "table" then + -- A new row shall only be inserted once. This is needed as we are + -- checking multiple columns that may each be too long + local bInsertedRow = false + local sCurrString = "" + + for nCol, sVal in pairs(vElement) do + if #sVal > tWidths[nCol] then + -- Now begin splitting up the string + for sWord, sSpaces in sVal:gmatch("([^%s]+)(%s*)") do + local nWordLen = #sWord + + if (#sCurrString + nWordLen + #sSpaces) <= tWidths[nCol] then + -- This entire segment will still fit + sCurrString = sCurrString .. sWord .. sSpaces + elseif (#sCurrString + nWordLen) <= tWidths[nCol] then + -- We can fit the word without the succeeding spaces + -- fit the rest on a new line (spaces will be left out) + sCurrString = sCurrString .. sWord + + tInput[nRow][nCol] = sCurrString + + -- Don't carry over the spaces (copy substring after word len + space len) + insertNewLine(bInsertedRow, tInput, nRow + 1, nCol, sVal:sub(#sCurrString + #sSpaces + 1)) + bInsertedRow = true + + break + else + if nWordLen > tWidths[nCol] then + -- Word needs to be split as it would not even fit on an empty column + -- This is so we don't endlessly try to fit it on a new line + sCurrString = sCurrString .. sWord:sub(1, tWidths[nCol] - #sCurrString) + end + + tInput[nRow][nCol] = sCurrString + insertNewLine(bInsertedRow, tInput, nRow + 1, nCol, sVal:sub(#sCurrString + 1)) + bInsertedRow = true + + break + end + end + end end + end + end +end + +local function tabulateCommon(bPaged, ...) + local tInput = {...} + + -- Do some first input sanitizing + for i = 1, #tInput do + expect(i, tInput[i], "number", "table") -- Either a color or column data + end - local cx, cy = term.getCursorPos() - cx = 1 + (nCol - 1) * nMaxLen - term.setCursorPos(cx, cy) - term.write(s) + local tMaxColumnWidths = {} + local tCopy = {} + + --[[ + This loop serves multiple purposes: + 1. Assure each row (table argument) only consists of string data + 2. Find out the maximum width for every single column + 3. Copy over the input to a new table, it will be mutated later + ]] + for nIndex, vElement in ipairs(tInput) do + if type(vElement) == "table" then -- Actual column data + tCopy[nIndex] = {} + + for nColumn, sValue in pairs(vElement) do + if type(sValue) ~= "string" then + error("Bad argument #" .. nIndex .. "." .. nColumn .. " (expected string, got " .. type(sValue) .. ")", 3) + end - nCol = nCol + 1 + tCopy[nIndex][nColumn] = sValue -- Copy over current column data + tMaxColumnWidths[nColumn] = math.max(tMaxColumnWidths[nColumn] or 0, #sValue) + end + else + -- Copy over color + tCopy[nIndex] = vElement end - print() end - for _, t in ipairs(tAll) do - if type(t) == "table" then - if #t > 0 then - drawCols(t) + + local nWidth, nHeight = term.getSize() + adjustColumnWidths(nWidth, tMaxColumnWidths) + chopUpColumns(tMaxColumnWidths, tCopy) + + local _, yPos = term.getCursorPos() + local nAvailableLines = math.max(nHeight - yPos, 0) -- Maybe, for some reason, cursor was below screen + + for _, nRow in ipairs(tCopy) do + if type(nRow) == "table" then + if nAvailableLines <= 0 then + if bPaged then + local nFGColor = term.getTextColor() + + term.setTextColor(colors.white) + term.setCursorPos(1, nHeight) + term.write("Press any key to continue") + term.setTextColor(nFGColor) + + os.pullEvent("key") + term.clearLine() + end + + term.scroll(1) + nAvailableLines = nAvailableLines + 1 + end + + local xPos = 1 + + for i = 1, #tMaxColumnWidths do + if nRow[i] then + term.setCursorPos(xPos, nHeight - nAvailableLines) + term.write(nRow[i]) + end + + xPos = xPos + tMaxColumnWidths[i] + 1 end - elseif type(t) == "number" then - term.setTextColor(t) + + nAvailableLines = nAvailableLines - 1 + else + term.setTextColor(nRow) end end + + -- Jump to next line so the cursor doesn't stay behind last written column + term.setCursorPos(1, nHeight - nAvailableLines) end --- Prints tables in a structured form. From 726cdeebca9246d9923f2258698673a4be3b10c2 Mon Sep 17 00:00:00 2001 From: wieselkatze Date: Wed, 5 Aug 2020 21:33:35 +0200 Subject: [PATCH 2/5] Make pagedTabulate print a whole page before prompt --- .../computercraft/lua/rom/apis/textutils.lua | 31 ++++++------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index 7839619284..048ab9aed0 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -325,46 +325,33 @@ local function tabulateCommon(bPaged, ...) chopUpColumns(tMaxColumnWidths, tCopy) local _, yPos = term.getCursorPos() - local nAvailableLines = math.max(nHeight - yPos, 0) -- Maybe, for some reason, cursor was below screen + local scroll = bPaged and makePagedScroll(term, yPos - 2) or term.scroll for _, nRow in ipairs(tCopy) do if type(nRow) == "table" then - if nAvailableLines <= 0 then - if bPaged then - local nFGColor = term.getTextColor() - - term.setTextColor(colors.white) - term.setCursorPos(1, nHeight) - term.write("Press any key to continue") - term.setTextColor(nFGColor) - - os.pullEvent("key") - term.clearLine() - end - - term.scroll(1) - nAvailableLines = nAvailableLines + 1 - end - local xPos = 1 - for i = 1, #tMaxColumnWidths do if nRow[i] then - term.setCursorPos(xPos, nHeight - nAvailableLines) + term.setCursorPos(xPos, yPos) term.write(nRow[i]) end xPos = xPos + tMaxColumnWidths[i] + 1 end - nAvailableLines = nAvailableLines - 1 + yPos = yPos + 1 + + if yPos > nHeight then + scroll(1) + yPos = yPos - 1 + end else term.setTextColor(nRow) end end -- Jump to next line so the cursor doesn't stay behind last written column - term.setCursorPos(1, nHeight - nAvailableLines) + term.setCursorPos(1, yPos) end --- Prints tables in a structured form. From d7cb414ea93794aab74291f23590af43c5ba8142 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Sun, 9 Aug 2020 21:50:58 +0100 Subject: [PATCH 3/5] Fix incorrect lower bound in mods.toml It appears I had failed to update this when last bumping the Forge version. Closes #521 - we're relying on a feature only added in Forge 31.1.16, and they're using 3.1.14. --- src/main/resources/META-INF/mods.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 94b6f2073e..451828625f 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -19,6 +19,6 @@ CC: Tweaked is a fork of ComputerCraft, adding programmable computers, turtles a [[dependencies.computercraft]] modId="forge" mandatory=true - versionRange="[31.0.13,32)" + versionRange="[31.1.41,32)" ordering="NONE" side="BOTH" From 966977bbd0c88810ab50c88f6f2c458514d14b74 Mon Sep 17 00:00:00 2001 From: wieselkatze Date: Mon, 10 Aug 2020 22:33:48 +0200 Subject: [PATCH 4/5] Switch to table.pack to catch nil arguments Switch to table.pack in order for tabulateCommon to properly error when one of the arguments provided is nil --- .../resources/data/computercraft/lua/rom/apis/textutils.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index 048ab9aed0..2b6bd5eedb 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -286,10 +286,10 @@ local function chopUpColumns(tWidths, tInput) end local function tabulateCommon(bPaged, ...) - local tInput = {...} + local tInput = table.pack(...) -- Do some first input sanitizing - for i = 1, #tInput do + for i = 1, tInput.n do expect(i, tInput[i], "number", "table") -- Either a color or column data end From 1c5bb66f2b3a4756963d0b0f7f7f875aa8f07e15 Mon Sep 17 00:00:00 2001 From: wieselkatze Date: Mon, 10 Aug 2020 22:42:36 +0200 Subject: [PATCH 5/5] Satisfy lua linter --- .../data/computercraft/lua/rom/apis/textutils.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua index 2b6bd5eedb..2aa64a9fd3 100644 --- a/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua +++ b/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua @@ -189,7 +189,7 @@ local function adjustColumnWidths(nWidth, tWidths) local nShrinkAmount if nNextSmallerElement then if tWidths[nMaxElement] ~= tWidths[nNextSmallerElement] then - nShrinkAmount = math.min(nSum - nWidth, (tWidths[nMaxElement] - tWidths[nNextSmallerElement])) + nShrinkAmount = math.min(nSum - nWidth, tWidths[nMaxElement] - tWidths[nNextSmallerElement]) else -- This will alternate between the two elements nShrinkAmount = 1 @@ -230,7 +230,7 @@ local function chopUpColumns(tWidths, tInput) local function insertNewLine(bInserted, tRows, nRow, nColumn, sText) if not bInserted then -- Only execute this the first time (upon creation of new line) - table.insert(tRows, nRow, {[nColumn] = sText}) + table.insert(tRows, nRow, { [nColumn] = sText }) return end @@ -250,10 +250,10 @@ local function chopUpColumns(tWidths, tInput) for sWord, sSpaces in sVal:gmatch("([^%s]+)(%s*)") do local nWordLen = #sWord - if (#sCurrString + nWordLen + #sSpaces) <= tWidths[nCol] then + if #sCurrString + nWordLen + #sSpaces <= tWidths[nCol] then -- This entire segment will still fit sCurrString = sCurrString .. sWord .. sSpaces - elseif (#sCurrString + nWordLen) <= tWidths[nCol] then + elseif #sCurrString + nWordLen <= tWidths[nCol] then -- We can fit the word without the succeeding spaces -- fit the rest on a new line (spaces will be left out) sCurrString = sCurrString .. sWord