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