mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 21:52:59 +00:00 
			
		
		
		
	Merge branch 'mc-1.15.x' into mc-1.16.x
This commit is contained in:
		| @@ -27,21 +27,24 @@ function setPath(_sPath) | ||||
|     sPath = _sPath | ||||
| end | ||||
|  | ||||
| local extensions = { "", ".md", ".txt" } | ||||
|  | ||||
| --- Returns the location of the help file for the given topic. | ||||
| -- | ||||
| -- @tparam string topic The topic to find | ||||
| -- @treturn string|nil The path to the given topic's help file, or `nil` if it | ||||
| -- cannot be found. | ||||
| -- @usage help.lookup("disk") | ||||
| function lookup(_sTopic) | ||||
|     expect(1, _sTopic, "string") | ||||
| function lookup(topic) | ||||
|     expect(1, topic, "string") | ||||
|     -- Look on the path variable | ||||
|     for sPath in string.gmatch(sPath, "[^:]+") do | ||||
|         sPath = fs.combine(sPath, _sTopic) | ||||
|         if fs.exists(sPath) and not fs.isDir(sPath) then | ||||
|             return sPath | ||||
|         elseif fs.exists(sPath .. ".txt") and not fs.isDir(sPath .. ".txt") then | ||||
|             return sPath .. ".txt" | ||||
|     for path in string.gmatch(sPath, "[^:]+") do | ||||
|         path = fs.combine(path, topic) | ||||
|         for _, extension in ipairs(extensions) do | ||||
|             local file = path .. extension | ||||
|             if fs.exists(file) and not fs.isDir(file) then | ||||
|                 return file | ||||
|             end | ||||
|         end | ||||
|     end | ||||
|  | ||||
| @@ -66,8 +69,11 @@ function topics() | ||||
|             for _, sFile in pairs(tList) do | ||||
|                 if string.sub(sFile, 1, 1) ~= "." then | ||||
|                     if not fs.isDir(fs.combine(sPath, sFile)) then | ||||
|                         if #sFile > 4 and sFile:sub(-4) == ".txt" then | ||||
|                             sFile = sFile:sub(1, -5) | ||||
|                         for i = 2, #extensions do | ||||
|                             local extension = extensions[i] | ||||
|                             if #sFile > #extension and sFile:sub(-#extension) == extension then | ||||
|                                 sFile = sFile:sub(1, -#extension - 1) | ||||
|                             end | ||||
|                         end | ||||
|                         tItems[sFile] = true | ||||
|                     end | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| craft is a program for Crafty Turtles. Craft will craft a stack of items using the current inventory. | ||||
|  | ||||
| ex: | ||||
| "craft" will craft as many items as possible | ||||
| "craft all" will craft as many items as possible | ||||
| "craft 5" will craft at most 5 times | ||||
|   | ||||
| @@ -29,8 +29,8 @@ local completion = require "cc.completion" | ||||
|  | ||||
| --- Complete the name of a file relative to the current working directory. | ||||
| -- | ||||
| -- @tparam table shell The shell we're completing in | ||||
| -- @tparam { string... } choices The list of choices to complete from. | ||||
| -- @tparam table shell The shell we're completing in. | ||||
| -- @tparam string text Current text to complete. | ||||
| -- @treturn { string... } A list of suffixes of matching files. | ||||
| local function file(shell, text) | ||||
|     return fs.complete(text, shell.dir(), true, false) | ||||
| @@ -38,8 +38,8 @@ end | ||||
|  | ||||
| --- Complete the name of a directory relative to the current working directory. | ||||
| -- | ||||
| -- @tparam table shell The shell we're completing in | ||||
| -- @tparam { string... } choices The list of choices to complete from. | ||||
| -- @tparam table shell The shell we're completing in. | ||||
| -- @tparam string text Current text to complete. | ||||
| -- @treturn { string... } A list of suffixes of matching directories. | ||||
| local function dir(shell, text) | ||||
|     return fs.complete(text, shell.dir(), false, true) | ||||
| @@ -48,8 +48,8 @@ end | ||||
| --- Complete the name of a file or directory relative to the current working | ||||
| -- directory. | ||||
| -- | ||||
| -- @tparam table shell The shell we're completing in | ||||
| -- @tparam { string... } choices The list of choices to complete from. | ||||
| -- @tparam table shell The shell we're completing in. | ||||
| -- @tparam string text Current text to complete. | ||||
| -- @tparam { string... } previous The shell arguments before this one. | ||||
| -- @tparam[opt] boolean add_space Whether to add a space after the completed item. | ||||
| -- @treturn { string... } A list of suffixes of matching files and directories. | ||||
| @@ -74,14 +74,46 @@ end | ||||
|  | ||||
| --- Complete the name of a program. | ||||
| -- | ||||
| -- @tparam table shell The shell we're completing in | ||||
| -- @tparam { string... } choices The list of choices to complete from. | ||||
| -- @tparam table shell The shell we're completing in. | ||||
| -- @tparam string text Current text to complete. | ||||
| -- @treturn { string... } A list of suffixes of matching programs. | ||||
| -- @see shell.completeProgram | ||||
| local function program(shell, text) | ||||
|     return shell.completeProgram(text) | ||||
| end | ||||
|  | ||||
| --- Complete arguments of a program. | ||||
| -- | ||||
| -- @tparam table shell The shell we're completing in. | ||||
| -- @tparam string text Current text to complete. | ||||
| -- @tparam { string... } previous The shell arguments before this one. | ||||
| -- @tparam number starting Which argument index this program and args start at. | ||||
| -- @treturn { string... } A list of suffixes of matching programs or arguments. | ||||
| local function programWithArgs(shell, text, previous, starting) | ||||
|     if #previous + 1 == starting then | ||||
|         local tCompletionInfo = shell.getCompletionInfo() | ||||
|         if text:sub(-1) ~= "/" and tCompletionInfo[shell.resolveProgram(text)] then | ||||
|             return { " " } | ||||
|         else | ||||
|             local results = shell.completeProgram(text) | ||||
|             for n = 1, #results do | ||||
|                 local sResult = results[n] | ||||
|                 if sResult:sub(-1) ~= "/" and tCompletionInfo[shell.resolveProgram(text .. sResult)] then | ||||
|                     results[n] = sResult .. " " | ||||
|                 end | ||||
|             end | ||||
|             return results | ||||
|         end | ||||
|     else | ||||
|         local program = previous[starting] | ||||
|         local resolved = shell.resolveProgram(program) | ||||
|         if not resolved then return end | ||||
|         local tCompletion = shell.getCompletionInfo()[resolved] | ||||
|         if not tCompletion then return end | ||||
|         return tCompletion.fnComplete(shell, #previous - starting + 1, text, { program, table.unpack(previous, starting + 1, #previous) }) | ||||
|     end | ||||
| end | ||||
|  | ||||
| --[[- A helper function for building shell completion arguments. | ||||
|  | ||||
| This accepts a series of single-argument completion functions, and combines | ||||
| @@ -144,6 +176,7 @@ return { | ||||
|     dir = dir, | ||||
|     dirOrFile = dirOrFile, | ||||
|     program = program, | ||||
|     programWithArgs = programWithArgs, | ||||
|  | ||||
|     -- Re-export various other functions | ||||
|     help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function. | ||||
|   | ||||
| @@ -14,16 +14,127 @@ if sTopic == "index" then | ||||
| end | ||||
|  | ||||
| local strings = require "cc.strings" | ||||
| local function word_wrap(text, width) | ||||
|     local lines = strings.wrap(text, width) | ||||
|  | ||||
| local function min_of(a, b, default) | ||||
|     if not a and not b then return default end | ||||
|     if not a then return b end | ||||
|     if not b then return a end | ||||
|     return math.min(a, b) | ||||
| end | ||||
|  | ||||
| --[[- Parse a markdown string, extracting headings and highlighting some basic | ||||
| constructs. | ||||
|  | ||||
| The implementation of this is horrible. SquidDev shouldn't be allowed to write | ||||
| parsers, especially ones they think might be "performance critical". | ||||
| ]] | ||||
| local function parse_markdown(text) | ||||
|     local len = #text | ||||
|     local oob = len + 1 | ||||
|  | ||||
|     -- Some patterns to match headers and bullets on the start of lines. | ||||
|     -- The `%f[^\n\0]` is some wonderful logic to match the start of a line /or/ | ||||
|     -- the start of the document. | ||||
|     local heading = "%f[^\n\0](#+ +)([^\n]*)" | ||||
|     local bullet = "%f[^\n\0]( *)[.*]( +)" | ||||
|     local code = "`([^`]+)`" | ||||
|  | ||||
|     local new_text, fg, bg = "", "", "" | ||||
|     local function append(txt, fore, back) | ||||
|         new_text = new_text .. txt | ||||
|         fg = fg .. (fore or "0"):rep(#txt) | ||||
|         bg = bg .. (back or "f"):rep(#txt) | ||||
|     end | ||||
|  | ||||
|     local next_header = text:find(heading) | ||||
|     local next_bullet = text:find(bullet) | ||||
|     local next_block = min_of(next_header, next_bullet, oob) | ||||
|  | ||||
|     local next_code, next_code_end = text:find(code) | ||||
|  | ||||
|     local sections = {} | ||||
|  | ||||
|     local start = 1 | ||||
|     while start <= len do | ||||
|         if start == next_block then | ||||
|             if start == next_header then | ||||
|                 local _, fin, head, content = text:find(heading, start) | ||||
|                 sections[#new_text + 1] = content | ||||
|                 append(head .. content, "4", "f") | ||||
|                 start = fin + 1 | ||||
|  | ||||
|                 next_header = text:find(heading, start) | ||||
|             else | ||||
|                 local _, fin, space, content = text:find(bullet, start) | ||||
|                 append(space .. "\7" .. content) | ||||
|                 start = fin + 1 | ||||
|  | ||||
|                 next_bullet = text:find(bullet, start) | ||||
|             end | ||||
|  | ||||
|             next_block = min_of(next_header, next_bullet, oob) | ||||
|         elseif next_code and next_code_end < next_block then | ||||
|             -- Basic inline code blocks | ||||
|             if start < next_code then append(text:sub(start, next_code - 1)) end | ||||
|             local content = text:match(code, next_code) | ||||
|             append(content, "0", "7") | ||||
|  | ||||
|             start = next_code_end + 1 | ||||
|             next_code, next_code_end = text:find(code, start) | ||||
|         else | ||||
|             -- Normal text | ||||
|             append(text:sub(start, next_block - 1)) | ||||
|             start = next_block | ||||
|  | ||||
|             -- Rescan for a new code block | ||||
|             if next_code then next_code, next_code_end = text:find(code, start) end | ||||
|         end | ||||
|     end | ||||
|  | ||||
|     return new_text, fg, bg, sections | ||||
| end | ||||
|  | ||||
| local function word_wrap_basic(text, width) | ||||
|     local lines, fg, bg = strings.wrap(text, width), {}, {} | ||||
|     local fg_line, bg_line = ("0"):rep(width), ("f"):rep(width) | ||||
|  | ||||
|     -- Normalise the strings suitable for use with blit. We could skip this and | ||||
|     -- just use term.write, but saves us a clearLine call. | ||||
|     for k, line in pairs(lines) do | ||||
|         lines[k] = strings.ensure_width(line, width) | ||||
|         fg[k] = fg_line | ||||
|         bg[k] = bg_line | ||||
|     end | ||||
|  | ||||
|     return lines | ||||
|     return lines, fg, bg, {} | ||||
| end | ||||
|  | ||||
| local function word_wrap_markdown(text, width) | ||||
|     -- Add in styling for Markdown-formatted text. | ||||
|     local text, fg, bg, sections = parse_markdown(text) | ||||
|  | ||||
|     local lines = strings.wrap(text, width) | ||||
|     local fglines, bglines, section_list, section_n = {}, {}, {}, 1 | ||||
|  | ||||
|     -- Normalise the strings suitable for use with blit. We could skip this and | ||||
|     -- just use term.write, but saves us a clearLine call. | ||||
|     local start = 1 | ||||
|     for k, line in pairs(lines) do | ||||
|         -- I hate this with a burning passion, but it works! | ||||
|         local pos = text:find(line, start, true) | ||||
|         lines[k], fglines[k], bglines[k] = | ||||
|             strings.ensure_width(line, width), | ||||
|             strings.ensure_width(fg:sub(pos, pos + #line), width), | ||||
|             strings.ensure_width(bg:sub(pos, pos + #line), width) | ||||
|  | ||||
|         if sections[pos] then | ||||
|             section_list[section_n], section_n = { content = sections[pos], offset = k - 1 }, section_n + 1 | ||||
|         end | ||||
|  | ||||
|         start = pos + 1 | ||||
|     end | ||||
|  | ||||
|     return lines, fglines, bglines, section_list | ||||
| end | ||||
|  | ||||
| local sFile = help.lookup(sTopic) | ||||
| @@ -33,31 +144,40 @@ if not file then | ||||
|     return | ||||
| end | ||||
|  | ||||
| local contents = file:read("*a"):gsub("(\n *)[-*]( +)", "%1\7%2") | ||||
| local contents = file:read("*a") | ||||
| file:close() | ||||
|  | ||||
| local word_wrap = sFile:sub(-3) == ".md" and word_wrap_markdown or word_wrap_basic | ||||
| local width, height = term.getSize() | ||||
| local lines = word_wrap(contents, width) | ||||
| local lines, fg, bg, sections = word_wrap(contents, width) | ||||
| local print_height = #lines | ||||
|  | ||||
| -- If we fit within the screen, just display without pagination. | ||||
| if print_height <= height then | ||||
|     print(contents) | ||||
|     local _, y = term.getCursorPos() | ||||
|     for i = 1, print_height do | ||||
|         if y + i - 1 > height then | ||||
|             term.scroll(1) | ||||
|             term.setCursorPos(1, height) | ||||
|         else | ||||
|             term.setCursorPos(1, y + i - 1) | ||||
|         end | ||||
|  | ||||
|         term.blit(lines[i], fg[i], bg[i]) | ||||
|     end | ||||
|     return | ||||
| end | ||||
|  | ||||
| local current_section = nil | ||||
| local offset = 0 | ||||
|  | ||||
| local function draw() | ||||
|     local fg, bg = ("0"):rep(width), ("f"):rep(width) | ||||
|     for y = 1, height - 1 do | ||||
|         term.setCursorPos(1, y) | ||||
|         if y + offset > print_height then | ||||
|             -- Should only happen if we resize the terminal to a larger one | ||||
|             -- than actually needed for the current text. | ||||
|             term.clearLine() | ||||
|         else | ||||
|             term.blit(lines[y + offset], fg, bg) | ||||
| --- Find the currently visible seciton, or nil if this document has no sections. | ||||
| -- | ||||
| -- This could potentially be a binary search, but right now it's not worth it. | ||||
| local function find_section() | ||||
|     for i = #sections, 1, -1 do | ||||
|         if sections[i].offset <= offset then | ||||
|             return i | ||||
|         end | ||||
|     end | ||||
| end | ||||
| @@ -68,7 +188,10 @@ local function draw_menu() | ||||
|     term.clearLine() | ||||
|  | ||||
|     local tag = "Help: " .. sTopic | ||||
|     term.write("Help: " .. sTopic) | ||||
|     if current_section then | ||||
|         tag = tag .. (" (%s)"):format(sections[current_section].content) | ||||
|     end | ||||
|     term.write(tag) | ||||
|  | ||||
|     if width >= #tag + 16 then | ||||
|         term.setCursorPos(width - 14, height) | ||||
| @@ -76,11 +199,31 @@ local function draw_menu() | ||||
|     end | ||||
| end | ||||
|  | ||||
|  | ||||
| local function draw() | ||||
|     for y = 1, height - 1 do | ||||
|         term.setCursorPos(1, y) | ||||
|         if y + offset > print_height then | ||||
|             -- Should only happen if we resize the terminal to a larger one | ||||
|             -- than actually needed for the current text. | ||||
|             term.clearLine() | ||||
|         else | ||||
|             term.blit(lines[y + offset], fg[y + offset], bg[y + offset]) | ||||
|         end | ||||
|     end | ||||
|  | ||||
|     local new_section = find_section() | ||||
|     if new_section ~= current_section then | ||||
|         current_section = new_section | ||||
|         draw_menu() | ||||
|     end | ||||
| end | ||||
|  | ||||
| draw() | ||||
| draw_menu() | ||||
|  | ||||
| while true do | ||||
|     local event, param = os.pullEvent() | ||||
|     local event, param = os.pullEventRaw() | ||||
|     if event == "key" then | ||||
|         if param == keys.up and offset > 0 then | ||||
|             offset = offset - 1 | ||||
| @@ -97,6 +240,12 @@ while true do | ||||
|         elseif param == keys.home then | ||||
|             offset = 0 | ||||
|             draw() | ||||
|         elseif param == keys.left and current_section and current_section > 1 then | ||||
|             offset = sections[current_section - 1].offset | ||||
|             draw() | ||||
|         elseif param == keys.right and current_section and current_section < #sections then | ||||
|             offset = sections[current_section + 1].offset | ||||
|             draw() | ||||
|         elseif param == keys["end"] then | ||||
|             offset = print_height - height | ||||
|             draw() | ||||
| @@ -124,6 +273,8 @@ while true do | ||||
|         offset = math.max(math.min(offset, print_height - height), 0) | ||||
|         draw() | ||||
|         draw_menu() | ||||
|     elseif event == "terminate" then | ||||
|         break | ||||
|     end | ||||
| end | ||||
|  | ||||
|   | ||||
| @@ -51,7 +51,7 @@ local function get(sUrl) | ||||
|  | ||||
|     local sResponse = response.readAll() | ||||
|     response.close() | ||||
|     return sResponse | ||||
|     return sResponse or "" | ||||
| end | ||||
|  | ||||
| if run then | ||||
| @@ -79,7 +79,12 @@ else | ||||
|     local res = get(url) | ||||
|     if not res then return end | ||||
|  | ||||
|     local file = fs.open(sPath, "wb") | ||||
|     local file, err = fs.open(sPath, "wb") | ||||
|     if not file then | ||||
|         printError("Cannot save file: " .. err) | ||||
|         return | ||||
|     end | ||||
|  | ||||
|     file.write(res) | ||||
|     file.close() | ||||
|  | ||||
|   | ||||
| @@ -7,7 +7,7 @@ local function printUsage() | ||||
| end | ||||
|  | ||||
| local tArgs = { ... } | ||||
| if #tArgs < 2 then | ||||
| if #tArgs < 2 or tArgs[1] == "scale" and #tArgs < 3 then | ||||
|     printUsage() | ||||
|     return | ||||
| end | ||||
| @@ -21,7 +21,7 @@ if tArgs[1] == "scale" then | ||||
|  | ||||
|     local nRes = tonumber(tArgs[3]) | ||||
|     if nRes == nil or nRes < 0.5 or nRes > 5 then | ||||
|         print("Invalid scale: " .. nRes) | ||||
|         print("Invalid scale: " .. tArgs[3]) | ||||
|         return | ||||
|     end | ||||
|  | ||||
|   | ||||
| @@ -9,20 +9,19 @@ if not turtle.craft then | ||||
| end | ||||
|  | ||||
| local tArgs = { ... } | ||||
| local nLimit = nil | ||||
| if #tArgs < 1 then | ||||
| local nLimit = tonumber(tArgs[1]) | ||||
|  | ||||
| if not nLimit and tArgs[1] ~= "all" then | ||||
|     local programName = arg[0] or fs.getName(shell.getRunningProgram()) | ||||
|     print("Usage: " .. programName .. " [number]") | ||||
|     print("Usage: " .. programName .. " all|<number>") | ||||
|     return | ||||
| else | ||||
|     nLimit = tonumber(tArgs[1]) | ||||
| end | ||||
|  | ||||
| local nCrafted = 0 | ||||
| local nOldCount = turtle.getItemCount(turtle.getSelectedSlot()) | ||||
| if turtle.craft(nLimit) then | ||||
|     local nNewCount = turtle.getItemCount(turtle.getSelectedSlot()) | ||||
|     if nOldCount <= nLimit then | ||||
|     if not nLimit or nOldCount <= nLimit then | ||||
|         nCrafted = nNewCount | ||||
|     else | ||||
|         nCrafted = nOldCount - nNewCount | ||||
|   | ||||
| @@ -81,9 +81,17 @@ shell.setCompletionFunction("rom/programs/monitor.lua", completion.build( | ||||
|         if previous[2] == "scale" then | ||||
|             return completion.peripheral(shell, text, previous, true) | ||||
|         else | ||||
|             return completion.program(shell, text, previous) | ||||
|             return completion.programWithArgs(shell, text, previous, 3) | ||||
|         end | ||||
|     end | ||||
|     end, | ||||
|     { | ||||
|         function(shell, text, previous) | ||||
|             if previous[2] ~= "scale" then | ||||
|                 return completion.programWithArgs(shell, text, previous, 3) | ||||
|             end | ||||
|         end, | ||||
|         many = true, | ||||
|     } | ||||
| )) | ||||
|  | ||||
| shell.setCompletionFunction("rom/programs/move.lua", completion.build( | ||||
| @@ -98,11 +106,11 @@ shell.setCompletionFunction("rom/programs/rename.lua", completion.build( | ||||
|     { completion.dirOrFile, true }, | ||||
|     completion.dirOrFile | ||||
| )) | ||||
| shell.setCompletionFunction("rom/programs/shell.lua", completion.build(completion.program)) | ||||
| shell.setCompletionFunction("rom/programs/shell.lua", completion.build({ completion.programWithArgs, 2, many = true })) | ||||
| shell.setCompletionFunction("rom/programs/type.lua", completion.build(completion.dirOrFile)) | ||||
| shell.setCompletionFunction("rom/programs/set.lua", completion.build({ completion.setting, true })) | ||||
| shell.setCompletionFunction("rom/programs/advanced/bg.lua", completion.build(completion.program)) | ||||
| shell.setCompletionFunction("rom/programs/advanced/fg.lua", completion.build(completion.program)) | ||||
| shell.setCompletionFunction("rom/programs/advanced/bg.lua", completion.build({ completion.programWithArgs, 2, many = true })) | ||||
| shell.setCompletionFunction("rom/programs/advanced/fg.lua", completion.build({ completion.programWithArgs, 2, many = true })) | ||||
| shell.setCompletionFunction("rom/programs/fun/dj.lua", completion.build( | ||||
|     { completion.choice, { "play", "play ", "stop " } }, | ||||
|     completion.peripheral | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates