Merge remote-tracking branch 'origin/master-MC1.7.10' into mfu

This commit is contained in:
Vexatos 2016-05-07 13:34:35 +02:00
commit c6578ff587
9 changed files with 396 additions and 212 deletions

View File

@ -33,14 +33,19 @@ function stdoutStream:write(str)
end
function stderrStream:write(str)
local component = require("component")
if component.isAvailable("gpu") and component.gpu.getDepth() and component.gpu.getDepth() > 1 then
local foreground = component.gpu.setForeground(0xFF0000)
term.write(str, true)
component.gpu.setForeground(foreground)
else
term.write(str, true)
local gpu = term.gpu()
local set_depth = gpu and gpu.getDepth() and gpu.getDepth() > 1
if set_depth then
set_depth = gpu.setForeground(0xFF0000)
end
term.drawText(str, true)
if set_depth then
gpu.setForeground(set_depth)
end
return self
end

View File

@ -0,0 +1,7 @@
require("filesystem").mount(
setmetatable({
isReadOnly = function()return true end
},
{
__index=function(tbl,key)return require("devfs")[key]end
}), "/dev")

View File

@ -0,0 +1,115 @@
local fs = require("filesystem")
local proxy = {points={},address=require("guid").next()}
local nop = function()end
function proxy.getLabel()
return "devfs"
end
function proxy.setLabel(value)
error("drive does not support labeling")
end
function proxy.isReadOnly()
return false
end
function proxy.spaceTotal()
return 0
end
function proxy.spaceUsed()
return 0
end
function proxy.exists(path)
return not not proxy.points[path]
end
function proxy.size(path)
return 0
end
function proxy.isDirectory(path)
return false
end
function proxy.lastModified(path)
return fs.lastModified("/dev/")
end
function proxy.list()
local keys = {}
for k,v in pairs(proxy.points) do
table.insert(keys, k)
end
return keys
end
function proxy.makeDirectory(path)
return false
end
function proxy.remove(path)
if not proxy.exists(path) then return false end
proxy.points[path] = nil
return true
end
function proxy.rename(from, to)
return false
end
proxy.close = nop
function proxy.open(path, mode)
checkArg(1, path, "string")
local handle = proxy.points[path]
if not handle then return nil, "device point [" .. path .. "] does not exist" end
local msg = "device point [" .. path .. "] cannot be opened for "
if mode == "r" then
if not handle.read then
return nil, msg .. "read"
end
else
if not handle.write then
return nil, msg .. "write"
end
end
return handle
end
function proxy.read(h,...)
return h:read(...)
end
function proxy.seek(h,...)
return h:seek(...)
end
function proxy.write(h,...)
return h:write(...)
end
function proxy.create(path, handle)
handle.close = handle.close or nop
proxy.points[path] = handle
return true
end
proxy.create("null", {write = nop})
proxy.create("random", {read = function(_,n)
local chars = {}
for i=1,n do
table.insert(chars,string.char(math.random(0,255)))
end
return table.concat(chars)
end})
return proxy

View File

@ -15,16 +15,14 @@ function pipeStream.new(pm)
return setmetatable(stream, metatable)
end
function pipeStream:resume()
local yield_args = table.pack(self.pm.pco.resume_all(table.unpack(self.pm.args)))
local yield_args = table.pack(self.pm.pco.resume_all())
if not yield_args[1] then
self.pm.args = {false}
self.pm.dead = true
if not yield_args[1] and yield_args[2] then
io.stderr:write(tostring(yield_args[2]) .. "\n")
end
end
self.pm.args = {true}
return table.unpack(yield_args)
end
function pipeStream:close()
@ -134,7 +132,6 @@ function plib.internal.create(fp)
local pco = setmetatable(
{
stack = {},
args = {},
next = nil,
create = _co.create,
wrap = _co.wrap,
@ -318,7 +315,7 @@ function pipeManager.new(prog, mode, env)
end
local pm = setmetatable(
{dead=false,closed=false,args={},prog=prog,mode=mode,env=env},
{dead=false,closed=false,prog=prog,mode=mode,env=env},
{__index=pipeManager}
)
pm.prog_id = pm.mode == "r" and 1 or 2
@ -328,37 +325,29 @@ function pipeManager.new(prog, mode, env)
function()pm.dead=true end
pm.commands = {}
pm.commands[pm.prog_id] = {shellPath, sh.internal.buildCommandRedirects({})}
pm.commands[pm.self_id] = {pm.handler, sh.internal.buildCommandRedirects({})}
pm.commands[pm.prog_id] = {shellPath, {}}
pm.commands[pm.self_id] = {pm.handler, {}}
pm.root = function()
local startup_args = {}
local reason
pm.threads, reason, pm.inputs, pm.outputs =
sh.internal.buildPipeStream(pm.commands, pm.env)
pm.threads, reason = sh.internal.createThreads(pm.commands, {}, pm.env)
if not pm.threads then
pm.dead = true
return false, reason -- 2nd return is reason, not pipes, on error :)
return false, reason
end
pm.pipe = reason[1] -- an array of pipes of length 1
local startup_args = {}
pm.pipe = process.info(pm.threads[1]).data.io[1]
process.info(pm.threads[pm.prog_id]).data.args = {pm.env,pm.prog}
-- if we are the writer, we need args to resume prog
if pm.mode == "w" then
pm.pipe.stream.args = {pm.env,pm.prog,n=2}
startup_args = {true,n=1}
-- also, if we are the writer, we need to intercept the reader
pm.pipe.stream.redirect.read = plib.internal.redirectRead(pm)
else
startup_args = {true,pm.env,pm.prog,n=3}
pm.pipe.stream.redirect[0] = plib.internal.redirectRead(pm)
end
return sh.internal.executePipeStream(
pm.threads,
{pm.pipe},
pm.inputs,
pm.outputs,
startup_args)
return sh.internal.runThreads(pm.threads)
end
return pm

View File

@ -81,7 +81,7 @@ function process.load(path, env, init, name)
return string.format('%s:\n%s', msg or '', stack)
end, ...)
}
process.list[thread] = nil
process.internal.close(thread)
if not result[1] then
-- msg can be a custom error object
local msg = result[2]
@ -100,6 +100,7 @@ function process.load(path, env, init, name)
env = env,
data = setmetatable(
{
handles = {},
io = setmetatable({}, {__index=p and p.data and p.data.io or nil}),
coroutine_handler = setmetatable({}, {__index=p and p.data and p.data.coroutine_handler or nil}),
}, {__index=p and p.data or nil}),
@ -133,4 +134,16 @@ function process.info(levelOrThread)
end
end
--table of undocumented api subject to change and intended for internal use
process.internal = {}
--this is a future stub for a more complete method to kill a process
function process.internal.close(thread)
checkArg(1,thread,"thread")
local pdata = process.info(thread).data
for k,v in pairs(pdata.handles) do
v:close()
end
process.list[thread] = nil
end
return process

View File

@ -8,13 +8,7 @@ local tx = require("transforms")
local unicode = require("unicode")
local sh = {}
sh.internal = setmetatable({},
{
__tostring=function()
return "table of undocumented api subject to change and intended for internal use"
end
})
sh.internal = {}
-- --[[@@]] are not just comments, but custom annotations for delayload methods.
-- See package.lua and the api wiki for more information
@ -133,29 +127,36 @@ function sh.internal.isIdentifier(key)
end
function sh.expand(value)
return value
local expanded = value
:gsub("%$([_%w%?]+)", function(key)
if key == "?" then
return tostring(sh.getLastExitCode())
end
return os.getenv(key) or '' end)
return os.getenv(key) or ''
end)
:gsub("%${(.*)}", function(key)
if sh.internal.isIdentifier(key) then
return sh.internal.expandKey(key)
end
error("${" .. key .. "}: bad substitution")
end)
if expanded:find('`') then
expanded = sh.internal.parse_sub(expanded)
end
return expanded
end
function sh.internal.expand(word)
if #word == 0 then return {} end
local result = ''
for i=1,#word do
local part = word[i]
result = result .. (not (part.qr and part.qr[3]) and sh.expand(part.txt) or part.txt)
-- sh.expand runs command substitution on backticks
-- if the entire quoted area is backtick quoted, then
-- we can save some checks by adding them back in
local q = part.qr and part.qr[1] == '`' and '`' or ''
result = result .. (not (part.qr and part.qr[3]) and sh.expand(q..part.txt..q) or part.txt)
end
return {result}
end
@ -205,49 +206,6 @@ function sh.hintHandler(full_line, cursor)
return sh.internal.hintHandlerImpl(full_line, cursor)
end
function sh.internal.buildCommandRedirects(args)
local input, output, mode = nil, nil, "write"
local tokens = args
args = {}
local function smt(call) -- state metatable factory
local function index(_, token)
if token == "<" or token == ">" or token == ">>" then
return "parse error near " .. token
end
call(token)
return "args" -- default, return to normal arg parsing
end
return {__index=index}
end
local sm = { -- state machine for redirect parsing
args = setmetatable({["<"]="input", [">"]="output", [">>"]="append"},
smt(function(token)
table.insert(args, token)
end)),
input = setmetatable({}, smt(function(token)
input = token
end)),
output = setmetatable({}, smt(function(token)
output = token
mode = "write"
end)),
append = setmetatable({}, smt(function(token)
output = token
mode = "append"
end))
}
-- Run state machine over tokens.
local state = "args"
for i = 1, #tokens do
local token = tokens[i]
state = sm[state][token]
if not sm[state] then
return nil, state
end
end
return args, input, output, mode
end
function sh.internal.parseCommand(words)
checkArg(1, words, "table")
if #words == 0 then
@ -263,10 +221,11 @@ function sh.internal.parseCommand(words)
if not program then
return nil, evaluated_words[1] .. ": " .. reason
end
return program, sh.internal.buildCommandRedirects(tx.sub(evaluated_words, 2))
evaluated_words = tx.sub(evaluated_words, 2)
return program, evaluated_words
end
function sh.internal.buildPipeStream(commands, env)
function sh.internal.createThreads(commands, eargs, env)
-- Piping data between programs works like so:
-- program1 gets its output replaced with our custom stream.
-- program2 gets its input replaced with our custom stream.
@ -274,109 +233,70 @@ function sh.internal.buildPipeStream(commands, env)
-- custom stream triggers execution of "next" program after write.
-- custom stream triggers yield before read if buffer is empty.
-- custom stream may have "redirect" entries for fallback/duplication.
local threads, pipes, inputs, outputs = {}, {}, {}, {}
local threads = {}
for i = 1, #commands do
local program, args, input, output, mode = table.unpack(commands[i])
local process_name = tostring(program)
local reason
local program, args = table.unpack(commands[i])
local name, thread = tostring(program)
local thread_env = type(program) == "string" and env or nil
threads[i], reason = process.load(program, thread_env, function()
os.setenv("_", program)
if input then
local file, reason = io.open(shell.resolve(input))
if not file then
error("could not open '" .. input .. "': " .. reason, 0)
end
table.insert(inputs, file)
if pipes[i - 1] then
pipes[i - 1].stream.redirect.read = file
io.input(pipes[i - 1])
else
io.input(file)
end
elseif pipes[i - 1] then
io.input(pipes[i - 1])
local thread, reason = process.load(program, thread_env, function()
os.setenv("_", name)
-- popen expects each process to first write an empty string
-- this is required for proper thread order
io.write('')
end, name)
threads[i] = thread
if thread then
-- smart check if ios should be loaded
if tx.first(args, function(token) return token == "<" or token:find(">") end) then
args, reason = sh.internal.buildCommandRedirects(thread, args)
end
if output then
local file, reason = io.open(shell.resolve(output), mode == "append" and "a" or "w")
if not file then
error("could not open '" .. output .. "': " .. reason, 0)
end
table.insert(outputs, file)
if pipes[i] then
pipes[i].stream.redirect.write = file
io.output(pipes[i])
else
io.output(file)
end
elseif pipes[i] then
io.output(pipes[i])
end
if not args or not thread then
for i,t in ipairs(threads) do
process.internal.close(t)
end
io.write('')
end, process_name)
if not threads[i] then
return false, reason
return nil, reason
end
if i < #commands then
pipes[i] = require("buffer").new("rw", sh.internal.newMemoryStream())
pipes[i]:setvbuf("no")
end
if i > 1 then
pipes[i - 1].stream.next = threads[i]
pipes[i - 1].stream.args = args
end
process.info(thread).data.args = tx.concat(args, eargs or {})
end
return threads, pipes, inputs, outputs
if #threads > 1 then
sh.internal.buildPipeChain(threads)
end
return threads
end
function sh.internal.executePipeStream(threads, pipes, inputs, outputs, args)
function sh.internal.runThreads(threads)
local result = {}
for i = 1, #threads do
-- Emulate CC behavior by making yields a filtered event.pull()
while args[1] and coroutine.status(threads[i]) ~= "dead" do
result = table.pack(coroutine.resume(threads[i], table.unpack(args, 2, args.n)))
if coroutine.status(threads[i]) ~= "dead" then
local action = result[2]
if action == nil or type(action) == "number" then
args = table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
else
args = table.pack(coroutine.yield(table.unpack(result, 2, result.n)))
end
local thread, args = threads[i]
while coroutine.status(thread) ~= "dead" do
args = args or process.info(thread).data.args
result = table.pack(coroutine.resume(thread, table.unpack(args)))
if coroutine.status(thread) ~= "dead" then
args = sh.internal.handleThreadYield(result)
-- in case this was the end of the line, args is returned
result = args
if table.remove(args, 1) then
break
end
end
end
if pipes[i] then
pcall(pipes[i].close, pipes[i])
end
if not result[1] then
if type(result[2]) == "table" and result[2].reason == "terminated" then
if result[2].code then
result[1] = true
result.n = 1
else
result[2] = "terminated"
end
elseif type(result[2]) == "string" then
result[2] = debug.traceback(threads[i], result[2])
end
sh.internal.handleThreadCrash(thread, result)
break
end
end
for _, input in ipairs(inputs) do input:close() end
for _, output in ipairs(outputs) do output:close() end
return table.unpack(result)
end
function sh.internal.executeStatement(env, commands, eargs)
local threads, pipes, inputs, outputs = sh.internal.buildPipeStream(commands, env)
if not threads then return false, pipes end
local args = tx.concat({true,n=1},commands[1][2] or {}, eargs)
return sh.internal.executePipeStream(threads, pipes, inputs, outputs, args)
end
function sh.internal.executePipes(pipe_parts, eargs)
function sh.internal.executePipes(pipe_parts, eargs, env)
local commands = {}
for i=1,#pipe_parts do
commands[i] = table.pack(sh.internal.parseCommand(pipe_parts[i]))
@ -388,9 +308,14 @@ function sh.internal.executePipes(pipe_parts, eargs)
return sh.internal.ec.parseCommand
end
end
local result = table.pack(sh.internal.executeStatement(env,commands,eargs))
local cmd_result = result[2]
if not result[1] then
local threads, reason = sh.internal.createThreads(commands, eargs, env)
if not threads then
io.stderr:write(reason,"\n")
return false
end
local result, cmd_result = sh.internal.runThreads(threads)
if not result then
if cmd_result then
if type(cmd_result) == "string" then
cmd_result = cmd_result:gsub("^/lib/process%.lua:%d+: /", '/')
@ -404,7 +329,6 @@ end
function sh.execute(env, command, ...)
checkArg(2, command, "string")
local eargs = {...}
if command:find("^%s*#") then return true, 0 end
local statements, reason = sh.internal.statements(command)
if not statements or statements == true then
@ -413,15 +337,105 @@ function sh.execute(env, command, ...)
return true, 0
end
local eargs = {...}
-- simple
if reason then
sh.internal.ec.last = sh.internal.command_result_as_code(sh.internal.executePipes(statements,eargs))
sh.internal.ec.last = sh.internal.command_result_as_code(sh.internal.executePipes(statements, eargs, env))
return true
end
return sh.internal.execute_complex(statements)
return sh.internal.execute_complex(statements, eargs, env)
end
function --[[@delayloaded-start@]] sh.internal.handleThreadYield(result)
local action = result[2]
if action == nil or type(action) == "number" then
return table.pack(pcall(event.pull, table.unpack(result, 2, result.n)))
else
return table.pack(coroutine.yield(table.unpack(result, 2, result.n)))
end
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.handleThreadCrash(thread, result)
if type(result[2]) == "table" and result[2].reason == "terminated" then
if result[2].code then
result[1] = true
result.n = 1
else
result[2] = "terminated"
end
elseif type(result[2]) == "string" then
result[2] = debug.traceback(thread, result[2])
end
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.buildCommandRedirects(thread, args)
local data = process.info(thread).data
local tokens, ios, handles = args, data.io, data.handles
args = {}
local from_io, to_io, mode
for i = 1, #tokens do
local token = tokens[i]
if token == "<" then
from_io = 0
mode = "r"
else
local first_index, last_index, from_io_txt, mode_txt, to_io_txt = token:find("(%d*)(>>?)(.*)")
if mode_txt then
mode = mode_txt == ">>" and "a" or "w"
from_io = from_io_txt and tonumber(from_io_txt) or 1
if to_io_txt ~= "" then
to_io = tonumber(to_io_txt:sub(2))
ios[from_io] = ios[to_io]
mode = nil
end
else -- just an arg
if not mode then
table.insert(args, token)
else
local file, reason = io.open(shell.resolve(token), mode)
if not file then
return nil, "could not open '" .. token .. "': " .. reason
end
table.insert(handles, file)
ios[from_io] = file
end
mode = nil
end
end
end
return args
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.buildPipeChain(threads)
local prev_pipe
for i=1,#threads do
local thread = threads[i]
local data = process.info(thread).data
local pio = data.io
local pipe
if i < #threads then
pipe = require("buffer").new("rw", sh.internal.newMemoryStream())
pipe:setvbuf("no")
pipe.stream.redirect[1] = rawget(pio, 1)
pio[1] = pipe
table.insert(data.handles, pipe)
end
if prev_pipe then
prev_pipe.stream.redirect[0] = rawget(pio, 0)
prev_pipe.stream.next = thread
pio[0] = prev_pipe
end
prev_pipe = pipe
end
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.glob(glob_pattern)
local segments = text.split(glob_pattern, {"/"}, true)
local hiddens = tx.select(segments,function(e)return e:match("^%%%.")==nil end)
@ -549,7 +563,7 @@ function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor
return {}
end
local result
local prefix, partial = line:match("^(.*=)(.*)$")
local prefix, partial = line:match("^(.*[=><])(.*)$")
if not prefix then prefix, partial = line:match("^(.+%s+)(.*)$") end
local partialPrefix = (partial or line)
local name = partialPrefix:gsub(".*/", "")
@ -583,10 +597,11 @@ function --[[@delayloaded-start@]] sh.internal.hasValidPiping(words, pipes)
return true
end
pipes = pipes or tx.sub(text.syntax, 2) -- first text syntax is ; which CAN be repeated
local semi_split = tx.find(text.syntax, {";"}) -- all symbols before ; in syntax CAN be repeated
pipes = pipes or tx.sub(text.syntax, semi_split + 1)
local pies = tx.select(words, function(parts, i, t)
return (#parts == 1 and tx.first(pipes, {{parts[1].txt}}) and true or false), i
local pies = tx.select(words, function(parts, i)
return #parts == 1 and #text.split(parts[1].txt, pipes, true) == 0 and true or false
end)
local bad_pipe
@ -718,6 +733,7 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
function memoryStream:close()
self.closed = true
self.redirect = {}
end
function memoryStream:seek()
@ -728,12 +744,12 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
if self.closed then
return nil -- eof
end
if self.redirect.read then
if self.redirect[0] then
-- popen could be using this code path
-- if that is the case, it is important to leave stream.buffer alone
return self.redirect.read:read(n)
return self.redirect[0]:read(n)
elseif self.buffer == "" then
self.args = table.pack(coroutine.yield(table.unpack(self.result)))
process.info(self.next).data.args = table.pack(coroutine.yield(table.unpack(self.result)))
end
local result = string.sub(self.buffer, 1, n)
self.buffer = string.sub(self.buffer, n + 1)
@ -741,16 +757,17 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
end
function memoryStream:write(value)
if not self.redirect.write and self.closed then
if not self.redirect[1] and self.closed then
-- if next is dead, ignore all writes
if coroutine.status(self.next) ~= "dead" then
error("attempt to use a closed stream")
end
elseif self.redirect.write then
return self.redirect.write:write(value)
elseif self.redirect[1] then
return self.redirect[1]:write(value)
elseif not self.closed then
self.buffer = self.buffer .. value
self.result = table.pack(coroutine.resume(self.next, table.unpack(self.args)))
local args = process.info(self.next).data.args
self.result = table.pack(coroutine.resume(self.next, table.unpack(args)))
if coroutine.status(self.next) == "dead" then
self:close()
end
@ -764,26 +781,52 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
end
local stream = {closed = false, buffer = "",
redirect = {}, result = {}, args = {}}
redirect = {}, result = {}}
local metatable = {__index = memoryStream,
__metatable = "memorystream"}
return setmetatable(stream, metatable)
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.execute_complex(statements)
function --[[@delayloaded-start@]] sh.internal.execute_complex(statements, eargs, env)
for si=1,#statements do local s = statements[si]
local chains = sh.internal.groupChains(s)
local last_code,br = sh.internal.boolean_executor(chains, function(chain, chain_index)
local last_code = sh.internal.boolean_executor(chains, function(chain, chain_index)
local pipe_parts = sh.internal.splitChains(chain)
return sh.internal.executePipes(pipe_parts,
chain_index == #chains and si == #statements and eargs or {})
local next_args = chain_index == #chains and si == #statements and eargs or {}
return sh.internal.executePipes(pipe_parts, next_args, env)
end)
if br then
io.stderr:write(br,"\n")
end
sh.internal.ec.last = sh.internal.command_result_as_code(last_code)
end
return true, br
return true
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.parse_sub(input)
-- cannot use gsub here becuase it is a [C] call, and io.popen needs to yield at times
local packed = {}
-- not using for i... because i can skip ahead
local i, len = 1, #input
while i < len do
local fi, si, capture = input:find("`([^`]*)`", i)
if not fi then
table.insert(packed, input:sub(i))
break
end
local sub = io.popen(capture)
local result = sub:read("*a")
sub:close()
-- all whitespace is replaced by single spaces
-- we requote the result because tokenize will respect this as text
table.insert(packed, (text.trim(result):gsub("%s+"," ")))
i = si+1
end
return table.concat(packed)
end --[[@delayloaded-end@]]
return sh, local_env

View File

@ -35,7 +35,7 @@ local function findFile(name, ext)
dir = fs.concat(fs.concat(dir, name), "..")
local name = fs.name(name)
local list = fs.list(dir)
if list then
if list and name then
local files = {}
for file in list do
files[file] = true

View File

@ -245,7 +245,7 @@ function term.readKeyboard(ops)
if db ~= false then draw("\n") end
term.internal.read_history(history,input)
return input.data.."\n"
elseif char==8 then
elseif code==keys.back then
input:update(-1)
elseif code==keys.left then
input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1)
@ -400,11 +400,31 @@ function --[[@delayloaded-start@]] term.internal.ctrl_movement(input, dir)
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] term.internal.onTouch(input,gx,gy)
input:move(math.huge)
if input.data == "" then return end
input:move(-math.huge)
local w = W()
gx,gy=gx-w.dx,gy-w.dy
local x2,y2,d = input.w.x,input.w.y,input.w.w
input:move((gy*d+gx)-(y2*d+x2))
local char_width_to_move = ((gy*d+gx)-(y2*d+x2))
if char_width_to_move <= 0 then return end
local total_wlen = unicode.wlen(input.data)
if char_width_to_move >= total_wlen then
input:move(math.huge)
else
local chars_to_move = unicode.wtrunc(input.data, char_width_to_move + 1)
input:move(unicode.len(chars_to_move))
end
-- fake white space can make the index off, redo adjustment for alignment
x2,y2,d = input.w.x,input.w.y,input.w.w
char_width_to_move = ((gy*d+gx)-(y2*d+x2))
if (char_width_to_move < 0) then
-- using char_width_to_move as a type of index is wrong, but large enough and helps to speed this up
local up_to_cursor = unicode.sub(input.data, input.index+char_width_to_move, input.index)
local full_wlen = unicode.wlen(up_to_cursor)
local without_tail = unicode.wtrunc(up_to_cursor, full_wlen + char_width_to_move + 1)
local chars_cut = unicode.len(up_to_cursor) - unicode.len(without_tail)
input:move(-chars_cut)
end
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] term.internal.build_horizontal_reader(input)

View File

@ -8,14 +8,8 @@ local text = {}
local local_env = {tx=tx,unicode=unicode}
text.internal = {}
setmetatable(text.internal,
{
__tostring=function()
return 'table of undocumented api subject to change and intended for internal use'
end
})
text.syntax = {";","&&","||","|",">>",">","<"}
text.syntax = {"^%d*>>?&%d+$",";","&&","||?","^%d*>>?",">>?","<"}
function --[[@delayloaded-start@]] text.detab(value, tabWidth)
checkArg(1, value, "string")
@ -163,11 +157,12 @@ function text.internal.tokenize(value, quotes, delimiters)
checkArg(1, value, "string")
checkArg(2, quotes, "table", "nil")
checkArg(3, delimiters, "table", "nil")
local custom = not not delimiters
delimiters = delimiters or text.syntax
local words, reason = text.internal.words(value, quotes)
local splitter = text.escapeMagic(table.concat(delimiters))
local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
if type(words) ~= "table" or
#splitter == 0 or
not value:find("["..splitter.."]") then
@ -182,7 +177,7 @@ function text.internal.words(input, quotes)
checkArg(1, input, "string")
checkArg(2, quotes, "table", "nil")
local qr = nil
quotes = quotes or {{"'","'",true},{'"','"'}}
quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
local function append(dst, txt, qr)
local size = #dst
if size == 0 or dst[size].qr ~= qr then
@ -253,9 +248,6 @@ function --[[@delayloaded-start@]] text.internal.splitWords(words, delimiters)
table.insert(split_words[#split_words], part)
next_word = false
end
local delimLookup = tx.select(delimiters, function(e,i)
return i, e
end)
for wi=1,#words do local word = words[wi]
next_word = true
for pi=1,#word do local part = word[pi]
@ -265,7 +257,7 @@ function --[[@delayloaded-start@]] text.internal.splitWords(words, delimiters)
else
local part_text_splits = text.split(part.txt, delimiters)
tx.foreach(part_text_splits, function(sub_txt, spi)
local delim = delimLookup[sub_txt]
local delim = #text.split(sub_txt, delimiters, true) == 0
next_word = next_word or delim
add_part({txt=sub_txt,qr=qr})
next_word = delim