diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua index c35e46249..2bab3714c 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/bin/sh.lua @@ -148,6 +148,76 @@ end local args, options = shell.parse(...) local history = {} +local lastSearch + +local function drawPrompt() + local foreground = component.gpu.setForeground(0xFF0000) + term.write(expand(os.getenv("PS1") or "$ ")) + component.gpu.setForeground(foreground) +end + +local function getMatchingPrograms(pattern) + local res = {} + for dir in string.gmatch(os.getenv("PATH"), "[a-zA-Z0-9/.]+") do + for file in fs.list(dir) do + if string.match(file, "^" .. pattern .. "(.+)[.]lua") then + res[#res+1] = file:match("(.+).lua") + end + end + end + return res +end + +local function getMatchingFiles(pattern) + local res = {} + local dir = fs.isDirectory(pattern) and pattern or fs.path(pattern) or "/" + local name = (dir == pattern) and "" or fs.name(pattern) or "" + for file in fs.list(dir) do + if string.match(file, "^" .. name) then + res[#res+1] = file + end + end + return res +end + +local function hintHandler(line) + local base, space, after = string.match(line, "(.+)(%s)(.+)") + local searchProgram = not base + if not base then + base = "" + after = line or "" + end + if searchProgram then + local matches + if after:find("[/.]") == 1 then + matches = getMatchingFiles(after) + if #matches == 1 then + lastSearch = "" + local ret = base .. (space or "") .. after .. matches[1]:gsub(after:match("[/]*(%w+)$"),"",1) + return ret:gsub("[^/]$","%1 ") + end + else + matches = getMatchingPrograms(after) + if #matches == 1 then + lastSearch = "" + return matches[1] .. " " + end + end + + if lastSearch == line then return matches end + else + local matches = getMatchingFiles(after) + + for k in ipairs(matches)do + local ret = base .. space .. after .. (matches[k]):gsub(after:match("[/]*(%w+)$") or "","",1) + matches[k] = ret:gsub("[^/]$","%1 "):gsub("/$","") + end + + return matches + end + lastSearch = line +end + if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then -- interactive shell. while true do @@ -158,10 +228,8 @@ if #args == 0 and (io.input() == io.stdin or options.i) and not options.c then term.clear() end while term.isAvailable() do - local foreground = component.gpu.setForeground(0xFF0000) - term.write(expand(os.getenv("PS1") or "$ ")) - component.gpu.setForeground(foreground) - local command = term.read(history) + drawPrompt() + local command = term.read(history, nil, hintHandler) if not command then term.write("exit\n") return -- eof diff --git a/src/main/resources/assets/opencomputers/loot/OpenOS/lib/term.lua b/src/main/resources/assets/opencomputers/loot/OpenOS/lib/term.lua index bcecef08d..27092c9e7 100644 --- a/src/main/resources/assets/opencomputers/loot/OpenOS/lib/term.lua +++ b/src/main/resources/assets/opencomputers/loot/OpenOS/lib/term.lua @@ -101,7 +101,7 @@ function term.isAvailable() return component.isAvailable("gpu") and component.isAvailable("screen") end -function term.read(history, dobreak) +function term.read(history, dobreak, hint, prompt) checkArg(1, history, "table", "nil") history = history or {} table.insert(history, "") @@ -109,6 +109,9 @@ function term.read(history, dobreak) local scrollX, scrollY = 0, #history - 1 local cursorX = 1 + local hintCache = (type(hint)=="table" and #hint > 1)and hint + local selectedHint = 0 + local function getCursor() return cursorX, 1 + scrollY end @@ -247,29 +250,61 @@ function term.read(history, dobreak) right(unicode.len(value)) end + local function tab() + if not hintCache then + if type(hint) == "function" then + local h = hint(line()) + if type(h) == "string" then + local _, cby = getCursor() + history[cby] = after + elseif type(h) == "table" and #h > 0 then + hintCache = h + selectedHint = 1 + local _, cby = getCursor() + history[cby] = hintCache[selectedHint] or "" + end + end + else + selectedHint = (selectedHint+1)<=#hintCache and (selectedHint+1) or 1 + local _, cby = getCursor() + history[cby] = hintCache[selectedHint] or "" + end + redraw() + ende() + end + + local function cleanHint() + if type(hint) ~= "table" then + hintCache = nil + end + end + local function onKeyDown(char, code) term.setCursorBlink(false) if code == keyboard.keys.back then - if left() then delete() end + if left() then delete() end cleanHint() elseif code == keyboard.keys.delete then - delete() + delete()cleanHint() elseif code == keyboard.keys.left then left() elseif code == keyboard.keys.right then right() elseif code == keyboard.keys.home then - home() + home()cleanHint() elseif code == keyboard.keys["end"] then - ende() + ende()cleanHint() elseif code == keyboard.keys.up then up() elseif code == keyboard.keys.down then down() + elseif code == keyboard.keys.tab and hint then + tab() elseif code == keyboard.keys.enter then local cbx, cby = getCursor() if cby ~= #history then -- bring entry to front history[#history] = line() table.remove(history, cby) + cleanHint() end return true, history[#history] .. "\n" elseif keyboard.isControlDown() and code == keyboard.keys.d then @@ -282,6 +317,7 @@ function term.read(history, dobreak) return true, nil elseif not keyboard.isControl(char) then insert(unicode.char(char)) + cleanHint() end term.setCursorBlink(true) term.setCursorBlink(true) -- force toggle to caret