diff options
author | 2017-05-11 17:28:14 -0500 | |
---|---|---|
committer | 2017-05-13 16:57:47 -0500 | |
commit | 4ebc18aeec6929bd1396daef1289df9821422834 (patch) | |
tree | 759538020e787b21eb45113acb485c628185871b /plugins/console/init.lua | |
parent | 3553a7bf422d50d331c2cf59c9405f190be41f28 (diff) |
linenoise: replace linenoise-ng with a different port that is simpler and uses a different UTF8 parser [Carl]
plugins/console: better completions [Carl]
Diffstat (limited to 'plugins/console/init.lua')
-rw-r--r-- | plugins/console/init.lua | 174 |
1 files changed, 139 insertions, 35 deletions
diff --git a/plugins/console/init.lua b/plugins/console/init.lua index c27e55164bc..1218cba914f 100644 --- a/plugins/console/init.lua +++ b/plugins/console/init.lua @@ -1,5 +1,6 @@ --- license:BSD-3-Clause --- copyright-holders:Carl +-- license:MIT +-- copyright-holders:Carl, Patrick Rapin, Reuben Thomas +-- completion from https://github.com/rrthomas/lua-rlcompleter local exports = {} exports.name = "console" exports.version = "0.0.1" @@ -14,6 +15,7 @@ function console.startplugin() local started = false local ln = require("linenoise") local preload = false + local matches = {} print(" _/ _/ _/_/ _/ _/ _/_/_/_/"); print(" _/_/ _/_/ _/ _/ _/_/ _/_/ _/ "); print(" _/ _/ _/ _/_/_/_/ _/ _/ _/ _/_/_/ "); @@ -27,13 +29,80 @@ function console.startplugin() ln.historysetmaxlen(10) local scr = [[ local ln = require('linenoise') -ln.setcompletion(function(c, str) - status = str +ln.setcompletion(function(c, str, pos) + status = str .. "\x01" .. tostring(pos) yield() - status:gsub('[^,]*', function(s) if s ~= '' then ln.addcompletion(c, s) end end) + ln.addcompletion(c, status:match("([^\x01]*)\x01(.*)")) end) return ln.linenoise('\x1b[1;36m[MAME]\x1b[0m> ') ]] + local keywords = { + 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', + 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', + 'return', 'then', 'true', 'until', 'while' + } + + -- Main completion function. It evaluates the current sub-expression + -- to determine its type. Currently supports tables fields, global + -- variables and function prototype completion. + local function contextual_list(expr, sep, str, word) + local function add(value) + value = tostring(value) + if value:match("^" .. word) then + matches[#matches + 1] = value + end + end + + -- This function is called in a context where a keyword or a global + -- variable can be inserted. Local variables cannot be listed! + local function add_globals() + for _, k in ipairs(keywords) do + add(k) + end + for k in pairs(_G) do + add(k) + end + end + + if expr and expr ~= "" then + local v = loadstring("return " .. expr) + if v then + err, v = pcall(v) + if (not err) or (not v) then + add_globals() + return + end + local t = type(v) + if sep == '.' or sep == ':' then + if t == 'table' then + for k, v in pairs(v) do + if type(k) == 'string' and (sep ~= ':' or type(v) == "function") then + add(k) + end + end + elseif t == 'userdata' then + for k, v in pairs(getmetatable(v)) do + if type(k) == 'string' and (sep ~= ':' or type(v) == "function") then + add(k) + end + end + end + elseif sep == '[' then + if t == 'table' then + for k in pairs(v) do + if type(k) == 'number' then + add(k .. "]") + end + end + if word ~= "" then add_globals() end + end + end + end + end + if #matches == 0 then + add_globals() + end + end local function find_unmatch(str, openpar, pair) local done = false @@ -56,46 +125,81 @@ return ln.linenoise('\x1b[1;36m[MAME]\x1b[0m> ') return str end - local function get_completions(str) - local comps = "," - local rest, dot, last = str:match("(.-)([.:]?)([^.:]*)$") - str = find_unmatch(str, "%(", "%b()") - str = find_unmatch(str, "%[", "%b[]") - local table = str:match("([%w_%.:%(%)%[%]]-)[:.][%w_]*$") - local err - if rest == "" or not table then - if dot == "" then - table = "_G" + -- This complex function tries to simplify the input line, by removing + -- literal strings, full table constructors and balanced groups of + -- parentheses. Returns the sub-expression preceding the word, the + -- separator item ( '.', ':', '[', '(' ) and the current string in case + -- of an unfinished string literal. + local function simplify_expression(expr, word) + -- Replace annoying sequences \' and \" inside literal strings + expr = expr:gsub("\\(['\"])", function (c) + return string.format("\\%03d", string.byte(c)) + end) + local curstring + -- Remove (finished and unfinished) literal strings + while true do + local idx1, _, equals = expr:find("%[(=*)%[") + local idx2, _, sign = expr:find("(['\"])") + if idx1 == nil and idx2 == nil then + break + end + local idx, startpat, endpat + if (idx1 or math.huge) < (idx2 or math.huge) then + idx, startpat, endpat = idx1, "%[" .. equals .. "%[", "%]" .. equals .. "%]" else - return comps + idx, startpat, endpat = idx2, sign, sign end - end - err, tablef = pcall(load("return " .. table)) - if (not err) or (not tablef) then - return comps - end - rest = rest .. dot - if type(tablef) == 'table' then - for k, v in pairs(tablef) do - if k:match("^" .. last) then - comps = comps .. "," .. rest .. k - end + if expr:sub(idx):find("^" .. startpat .. ".-" .. endpat) then + expr = expr:gsub(startpat .. "(.-)" .. endpat, " STRING ") + else + expr = expr:gsub(startpat .. "(.*)", function (str) + curstring = str + return "(CURSTRING " + end) end end - if type(tablef) == "userdata" then - local tablef = getmetatable(tablef) - for k, v in pairs(tablef) do - if k:match("^" .. last) then - comps = comps .. "," .. rest .. k - end + -- crop string at unmatched open paran + expr = find_unmatch(expr, "%(", "%b()") + expr = find_unmatch(expr, "%[", "%b[]") + --expr = expr:gsub("%b()"," PAREN ") -- Remove groups of parentheses + expr = expr:gsub("%b{}"," TABLE ") -- Remove table constructors + -- Avoid two consecutive words without operator + expr = expr:gsub("(%w)%s+(%w)","%1|%2") + expr = expr:gsub("%s+", "") -- Remove now useless spaces + -- This main regular expression looks for table indexes and function calls. + return curstring, expr:match("([%.:%w%(%)%[%]_]-)([:%.%[%(])" .. word .. "$") + end + + local function get_completions(line, endpos) + matches = {} + local endstr = line:sub(endpos + 1, -1) + line = line:sub(1, endpos) + endstr = endstr or "" + local start, word = line:match("^(.*[ \t\n\"\\'><=;:%+%-%*/%%^~#{}%(%)%[%].,])(.-)$") + if not start then + start = "" + word = word or line + else + word = word or "" + end + + local str, expr, sep = simplify_expression(line, word) + contextual_list(expr, sep, str, word) + if #matches > 1 then + print("\n") + for k, v in pairs(matches) do + print(v) end + return "\x01" .. "-1" + elseif #matches == 1 then + return start .. matches[1] .. endstr .. "\x01" .. (#start + #matches[1]) end - return comps + return "\x01" .. "-1" end emu.register_periodic(function() if conth.yield then - conth:continue(get_completions(conth.result)) + conth:continue(get_completions(conth.result:match("([^\x01]*)\x01(.*)"))) return elseif conth.busy then return |