From 20b750ba71170bd5ff5b97d6b6d675d3c9f8ae06 Mon Sep 17 00:00:00 2001 From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54> Date: Fri, 10 Feb 2012 16:57:03 +0000 Subject: [PATCH] Debug console: Cursor positioning (left/right arrows, home/end to move to beginning/end of line) Debug console: Copy to system clipboard support, bound to Ctrl+C and matches Ctrl+V paste support Debug console: Tab-autocompletion of table entries git-svn-id: http://svn.net-core.org/repos/t-engine4@4826 51575b47-30f0-44d4-a5cc-537603b46e54 --- game/engines/default/engine/DebugConsole.lua | 148 ++++++++++++++++++- 1 file changed, 143 insertions(+), 5 deletions(-) diff --git a/game/engines/default/engine/DebugConsole.lua b/game/engines/default/engine/DebugConsole.lua index 0ebc324626..6a816d6c68 100644 --- a/game/engines/default/engine/DebugConsole.lua +++ b/game/engines/default/engine/DebugConsole.lua @@ -26,6 +26,7 @@ module(..., package.seeall, class.inherit(engine.Dialog)) offset = 0 history = {} line = "" +line_pos = 0 com_sel = 0 commands = {} @@ -57,12 +58,16 @@ function _M:init() end end self.line = "" + self.line_pos = 0 self.offset = 0 self.changed = true end, _UP = function() - self.com_sel = util.bound(self.com_sel - 1, 1, #self.commands) + self.com_sel = util.bound(self.com_sel - 1, 0, #self.commands) if self.commands[self.com_sel] then + if #self.line == 0 or self.line_pos == #self.line then + self.line_pos = #self.commands[self.com_sel] + end self.line = self.commands[self.com_sel] end self.changed = true @@ -70,27 +75,160 @@ function _M:init() _DOWN = function() self.com_sel = util.bound(self.com_sel + 1, 1, #self.commands) if self.commands[self.com_sel] then + if #self.line == 0 or self.line_pos == #self.line then + self.line_pos = #self.commands[self.com_sel] + end self.line = self.commands[self.com_sel] + else + self.line = "" + self.line_pos = 0 end self.changed = true end, + _LEFT = function() + self.line_pos = util.bound(self.line_pos - 1, 0, #self.line) + self.changed = true + end, + _RIGHT = function() + self.line_pos = util.bound(self.line_pos + 1, 0, #self.line) + self.changed = true + end, + _HOME = function() + self.line_pos = 0 + self.changed = true + end, + _END = function() + self.line_pos = #self.line + self.changed = true + end, _ESCAPE = function() game:unregisterDialog(self) end, _BACKSPACE = function() - self.line = self.line:sub(1, self.line:len() - 1) self.changed = true + self.line = self.line:sub(1, self.line_pos - 1) .. self.line:sub(self.line_pos + 1) + self.line_pos = util.bound(self.line_pos - 1, 0, #self.line) + self.changed = true end, __TEXTINPUT = function(c) - self.line = self.line .. c + self.line = self.line:sub(1, self.line_pos) .. c .. self.line:sub(self.line_pos + 1) + self.line_pos = util.bound(self.line_pos + 1, 0, #self.line) self.changed = true end, [{"_v", "ctrl"}] = function(c) local s = core.key.getClipboard() if s then - self.line = self.line .. s + self.line = self.line:sub(1, self.line_pos) .. s .. self.line:sub(self.line_pos + 1) + self.line_pos = util.bound(self.line_pos + #s, 0, #self.line) self.changed = true end end, + [{"_c", "ctrl"}] = function(c) + core.key.setClipboard(self.line) + end, + _TAB = function() + local find_base + find_base = function(remaining) + -- Don't try to auto-complete strings, check by counting quotation marks + local _, nsinglequote = remaining:gsub("\'", "") + local _, ndoublequote = remaining:gsub("\"", "") + if (nsinglequote % 2 ~= 0) or (ndoublequote % 2 ~= 0) then + return nil, "Cannot auto-complete strings." + end + -- Work from the back of the line to the front + local string_to_complete = remaining:match("[%d%w_%[%]%.:\'\"]+$") or "" + -- Find the trailing tail + local tail = string_to_complete:match("[%d%w_]+$") or "" + local linking_char = string_to_complete:sub(#string_to_complete - #tail, #string_to_complete - #tail) + -- Only handle numerical keys to auto-complete + if linking_char == "[" and not tonumber(tail) then + return find_base(tail) + end + -- Drop the linking character + local head = string_to_complete:sub(1, util.bound(#string_to_complete - #tail - 1, 0)) + if #head > 0 then + local f, err = loadstring("return " .. head) + if err then + return nil, err + else + local res = {pcall(f)} + if res[1] and res[2] then + return res[2], tail + else + return nil, ([[%s does not exist.]]):format(head) + end + end + -- Global namespace if there is no head + else + return _G, tail + end + end + local base, to_complete = find_base(self.line) + if not base then + if to_complete then + table.insert(self.history, ([[----- %s -----]]):format(to_complete)) + self.changed = true + end + return + end + local set = {} + local recurs_bases + recurs_bases = function(base) + for k, v in pairs(base) do + -- Need to handle numbers, too + if type(k) == "number" and tonumber(to_complete) then + if tostring(k):match("^" .. to_complete) then + set[tostring(k)] = true + end + elseif type(k) == "string" then + if k:match("^" .. to_complete) then + set[k] = true + end + end + end + -- Check the metatable __index + local mt = getmetatable(base) + if mt and mt.__index and type(mt.__index) == "table" then + recurs_bases(mt.__index) + end + end + recurs_bases(base) + -- Convert to a sorted array + local array = {} + for k, _ in pairs(set) do + array[#array+1] = k + end + table.sort(array, function(a, b) return a < b end) + -- If there is one possibility, complete it + if #array == 1 then + self.line = self.line:sub(1, #self.line - #to_complete) .. array[1] + self.line_pos = self.line_pos - #to_complete + #array[1] + elseif #array > 1 then + table.insert(self.history, "----- Auto-complete possibilities: -----") + for i, k in ipairs(array) do + table.insert(self.history, k) + end + -- Find the longest common substring and complete it + local substring = array[1]:sub(#to_complete+1) + for i=2,#array do + local min_len = math.min(#array[i], #substring) + for j=#to_complete,min_len do + if substring:sub(j, j) ~= array[i]:sub(j, j) then + substring = substring:sub(#to_complete+1, util.bound(j-1, 0)) + break + end + end + if #substring == 0 then break end + end + -- Complete to the longest common substring + if #substring > 0 then + self.line = self.line .. substring + self.line_pos = self.line_pos + #substring + end + else + table.insert(self.history, "----- No auto-complete possibilities. -----") + end + self.changed = true + end, } -- Scroll message log self:mouseZones{ @@ -107,7 +245,7 @@ function _M:drawDialog(s, w, h) local i, dh = #self.history - self.offset, self.ih - buffer - self.font:lineSkip() -- Start at the bottom and work up -- Draw the current command - s:drawStringBlended(self.font, self.line, 0, dh, 255, 255, 255) + s:drawStringBlended(self.font, self.line:sub(1, self.line_pos) .. "|" .. self.line:sub(self.line_pos+1), 0, dh, 255, 255, 255) dh = dh - self.font:lineSkip() -- Now draw the history with any offset while dh > buffer do -- GitLab