584 lines
11 KiB
Lua
584 lines
11 KiB
Lua
-- Based on MDave's thing
|
|
-- https://gist.github.com/mentlerd/d56ad9e6361f4b86af84
|
|
if SERVER then AddCSLuaFile() end
|
|
|
|
local type_weight = {
|
|
[TYPE_FUNCTION] = 1,
|
|
[TYPE_TABLE] = 2,
|
|
}
|
|
|
|
local type_colors = {
|
|
[TYPE_BOOL] = Color(175, 130, 255),
|
|
[TYPE_NUMBER] = Color(175, 130, 255),
|
|
[TYPE_STRING] = Color(230, 220, 115),
|
|
[TYPE_FUNCTION] = Color(100, 220, 240)
|
|
}
|
|
|
|
local color_neutral = Color(220, 220, 220)
|
|
local color_name = Color(260, 150, 30)
|
|
|
|
local color_reference = Color(150, 230, 50)
|
|
local color_comment = Color( 30, 210, 30)
|
|
|
|
-- 'nil' value
|
|
local NIL = {}
|
|
|
|
-- Localise for faster access
|
|
local pcall = pcall
|
|
|
|
local string_len = string.len
|
|
local string_sub = string.sub
|
|
local string_find = string.find
|
|
|
|
local table_concat = table.concat
|
|
local table_insert = table.insert
|
|
local table_sort = table.sort
|
|
|
|
|
|
-- Stream interface
|
|
local gMsgF -- Print fragment
|
|
local gMsgN -- Print newline
|
|
local gMsgC -- Set print color
|
|
|
|
local PrintLocals, gBegin, gFinish, PrintTableGrep
|
|
|
|
do
|
|
local grep_color = Color(235, 70, 70)
|
|
|
|
-- Grep parameters (static between gBegin/gEnd)
|
|
local grep
|
|
local grep_raw
|
|
|
|
local grep_proximity
|
|
|
|
|
|
-- Current line parameters
|
|
local buffer
|
|
local colors
|
|
local markers
|
|
|
|
local baseColor
|
|
local currColor
|
|
|
|
local length
|
|
|
|
-- History
|
|
local history
|
|
local remain
|
|
|
|
|
|
-- Actual printing
|
|
local function gCheckMatch( buffer )
|
|
local raw = table_concat(buffer)
|
|
|
|
return raw, string_find(raw, grep, 0, grep_raw)
|
|
end
|
|
|
|
local function gFlushEx( raw, markers, colors, baseColor )
|
|
|
|
-- Print entire buffer
|
|
local len = string_len(raw)
|
|
|
|
-- Keep track of the current line properties
|
|
local index = 1
|
|
local marker = 1
|
|
|
|
local currColor = baseColor
|
|
|
|
-- Method to print to a preset area
|
|
local function printToIndex( limit, color )
|
|
local mark = markers and markers[marker]
|
|
|
|
-- Print all marker areas until we would overshoot
|
|
while mark and mark < limit do
|
|
|
|
-- Catch up to the marker
|
|
MsgC(color or currColor or color_neutral, string_sub(raw, index, mark))
|
|
index = mark +1
|
|
|
|
-- Set new color
|
|
currColor = colors[marker]
|
|
|
|
-- Select next marker
|
|
marker = marker +1
|
|
mark = markers[marker]
|
|
|
|
end
|
|
|
|
-- Print the remaining between the last marker and the limit
|
|
MsgC(color or currColor or color_neutral, string_sub(raw, index, limit))
|
|
index = limit +1
|
|
end
|
|
|
|
-- Grep!
|
|
local match, last = 1
|
|
local from, to = string_find(raw, grep, 0, grep_raw)
|
|
|
|
while from do
|
|
printToIndex(from -1)
|
|
printToIndex(to, grep_color)
|
|
|
|
last = to +1
|
|
from, to = string_find(raw, grep, last, grep_raw)
|
|
end
|
|
|
|
printToIndex(len)
|
|
MsgN()
|
|
end
|
|
|
|
|
|
local function gCommit()
|
|
if grep_proximity then
|
|
-- Check if the line has at least one match
|
|
local raw, match = gCheckMatch(buffer)
|
|
|
|
if match then
|
|
|
|
-- Divide matches
|
|
if history[grep_proximity] then
|
|
MsgN("...")
|
|
end
|
|
|
|
-- Flush history
|
|
if grep_proximity ~= 0 then
|
|
local len = #history
|
|
|
|
for index = len -1, 1, -1 do
|
|
local entry = history[index]
|
|
history[index] = nil
|
|
|
|
gFlushEx( entry[1], entry[2], entry[3], entry[4] )
|
|
end
|
|
|
|
history[len] = nil
|
|
end
|
|
|
|
-- Flush line, allow next X lines to get printed
|
|
gFlushEx( raw, markers, colors, baseColor )
|
|
remain = grep_proximity -1
|
|
|
|
history[grep_proximity +1] = nil
|
|
elseif remain > 0 then
|
|
-- Flush immediately
|
|
gFlushEx( raw, markers, colors, baseColor )
|
|
remain = remain -1
|
|
else
|
|
-- Store in history
|
|
table_insert(history, 1, {raw, markers, colors, baseColor})
|
|
history[grep_proximity +1] = nil
|
|
end
|
|
else
|
|
-- Flush anyway
|
|
gFlushEx( table_concat(buffer), markers, colors, baseColor )
|
|
end
|
|
|
|
-- Reset state
|
|
length = 0
|
|
buffer = {}
|
|
|
|
markers = nil
|
|
colors = nil
|
|
|
|
baseColor = nil
|
|
currColor = nil
|
|
end
|
|
|
|
-- State machine
|
|
function gBegin( new, prox )
|
|
grep = isstring(new) and new
|
|
|
|
if grep then
|
|
grep_raw = not pcall(string_find, ' ', grep)
|
|
grep_proximity = isnumber(prox) and prox
|
|
|
|
-- Reset everything
|
|
buffer = {}
|
|
history = {}
|
|
end
|
|
|
|
length = 0
|
|
remain = 0
|
|
|
|
baseColor = nil
|
|
currColor = nil
|
|
end
|
|
|
|
function gFinish()
|
|
if grep_proximity and history and history[1] then
|
|
MsgN("...")
|
|
end
|
|
|
|
-- Free memory
|
|
buffer = nil
|
|
markers = nil
|
|
colors = nil
|
|
|
|
history = nil
|
|
end
|
|
|
|
|
|
function gMsgC( color )
|
|
if grep then
|
|
|
|
-- Try to save some memory by not immediately allocating colors
|
|
if length == 0 then
|
|
baseColor = color
|
|
return
|
|
end
|
|
|
|
-- Record color change
|
|
if color ~= currColor then
|
|
if not markers then
|
|
markers = {}
|
|
colors = {}
|
|
end
|
|
|
|
-- Record color change
|
|
table_insert(markers, length)
|
|
table_insert(colors, color)
|
|
end
|
|
end
|
|
|
|
currColor = color
|
|
end
|
|
|
|
function gMsgF( str )
|
|
if grep then
|
|
|
|
-- Split multiline fragments to separate ones
|
|
local fragColor = currColor or baseColor
|
|
|
|
local last = 1
|
|
local from, to = string_find(str, '\n')
|
|
|
|
while from do
|
|
local frag = string_sub(str, last, from -1)
|
|
local len = from - last
|
|
|
|
-- Merge fragment to the line
|
|
length = length + len
|
|
table_insert(buffer, frag)
|
|
|
|
-- Print finished line
|
|
gCommit()
|
|
|
|
-- Assign base color as previous fragColor
|
|
baseColor = fragColor
|
|
|
|
-- Look for more
|
|
last = to +1
|
|
from, to = string_find(str, '\n', last)
|
|
end
|
|
|
|
-- Push last fragment
|
|
local frag = string_sub(str, last)
|
|
local len = string_len(str) - last +1
|
|
|
|
length = length + len
|
|
table_insert(buffer, frag)
|
|
else
|
|
-- Push immediately
|
|
MsgC(currColor or baseColor or color_neutral, str)
|
|
end
|
|
end
|
|
|
|
function gMsgN()
|
|
-- Print everything in the buffer
|
|
if grep then
|
|
gCommit()
|
|
else
|
|
MsgN()
|
|
end
|
|
|
|
baseColor = nil
|
|
currColor = nil
|
|
end
|
|
end
|
|
|
|
|
|
local function InternalPrintValue( value )
|
|
|
|
-- 'nil' values can also be printed
|
|
if value == NIL then
|
|
gMsgC(color_comment)
|
|
gMsgF("nil")
|
|
return
|
|
end
|
|
|
|
local color = type_colors[ TypeID(value) ]
|
|
|
|
-- For strings, place quotes
|
|
if isstring(value) then
|
|
if string_len(value) <= 1 then
|
|
value = string.format([['%s']], value)
|
|
else
|
|
value = string.format([["%s"]], value)
|
|
end
|
|
|
|
gMsgC(color)
|
|
gMsgF(value)
|
|
return
|
|
end
|
|
|
|
-- Workaround for userdata not using MetaName
|
|
if string_sub(tostring(value), 0, 8) == "userdata" then
|
|
local meta = getmetatable(value)
|
|
|
|
if meta and meta.MetaName then
|
|
value = string.format("%s: %p", meta.MetaName, value)
|
|
end
|
|
end
|
|
|
|
-- General print
|
|
gMsgC(color)
|
|
gMsgF(tostring(value))
|
|
|
|
-- For functions append source info
|
|
if isfunction(value) then
|
|
local info = debug.getinfo(value, 'S')
|
|
local aux
|
|
|
|
if info.what == 'C' then
|
|
aux = "\t-- [C]: -1"
|
|
else
|
|
if info.linedefined ~= info.lastlinedefined then
|
|
aux = string.format("\t-- [%s]: %i-%i", info.short_src, info.linedefined, info.lastlinedefined)
|
|
else
|
|
aux = string.format("\t-- [%s]: %i", info.short_src, info.linedefined)
|
|
end
|
|
end
|
|
|
|
gMsgC(color_comment)
|
|
gMsgF(aux)
|
|
end
|
|
end
|
|
|
|
|
|
-- Associated to object keys
|
|
local objID
|
|
|
|
local function isprimitive( value )
|
|
local id = TypeID(value)
|
|
|
|
return id <= TYPE_FUNCTION and id ~= TYPE_TABLE
|
|
end
|
|
|
|
local function InternalPrintTable( table, path, prefix, names, todo )
|
|
|
|
-- Collect keys and some info about them
|
|
local keyList = {}
|
|
local keyStr = {}
|
|
|
|
local keyCount = 0
|
|
|
|
for key, value in pairs( table ) do
|
|
-- Add to key list for later sorting
|
|
table_insert(keyList, key)
|
|
|
|
-- Describe key as string
|
|
if isprimitive(key) then
|
|
keyStr[key] = tostring(key)
|
|
else
|
|
-- Lookup already known name
|
|
local name = names[key]
|
|
|
|
-- Assign a new unique identifier
|
|
if not name then
|
|
objID = objID +1
|
|
name = string.format("%s: obj #%i", tostring(key), objID)
|
|
|
|
names[key] = name
|
|
todo[key] = true
|
|
end
|
|
|
|
-- Substitute object with name
|
|
keyStr[key] = name
|
|
end
|
|
|
|
keyCount = keyCount +1
|
|
end
|
|
|
|
|
|
-- Exit early for empty tables
|
|
if keyCount == 0 then
|
|
return
|
|
end
|
|
|
|
|
|
-- Determine max key length
|
|
local keyLen = 4
|
|
|
|
for key, str in pairs(keyStr) do
|
|
keyLen = math.max(keyLen, string.len(str))
|
|
end
|
|
|
|
-- Sort table keys
|
|
if keyCount > 1 then
|
|
table_sort( keyList, function( A, B )
|
|
|
|
-- Sort numbers numerically correct
|
|
if isnumber(A) and isnumber(B) then
|
|
return A < B
|
|
end
|
|
|
|
-- Weight types
|
|
local wA = type_weight[ TypeID( table[A] ) ] or 0
|
|
local wB = type_weight[ TypeID( table[B] ) ] or 0
|
|
|
|
if wA ~= wB then
|
|
return wA < wB
|
|
end
|
|
|
|
-- Order by string representation
|
|
return keyStr[A] < keyStr[B]
|
|
|
|
end )
|
|
end
|
|
|
|
-- Determine the next level ident
|
|
local new_prefix = string.format( "%s║%s", prefix, string.rep(' ', keyLen) )
|
|
|
|
-- Mark object as done
|
|
todo[table] = nil
|
|
|
|
-- Start describing table
|
|
for index, key in ipairs(keyList) do
|
|
local value = table[key]
|
|
|
|
-- Assign names to already described keys/values
|
|
local kName = names[key]
|
|
local vName = names[value]
|
|
|
|
-- Decide to either fully describe, or print the value
|
|
local describe = not isprimitive(value) and ( not vName or todo[value] )
|
|
|
|
-- Ident
|
|
gMsgF(prefix)
|
|
|
|
-- Fancy table guides
|
|
local moreLines = (index ~= keyCount) or describe
|
|
|
|
if index == 1 then
|
|
gMsgF(moreLines and '╦ ' or '═ ')
|
|
else
|
|
gMsgF(moreLines and '╠ ' or '╚ ')
|
|
end
|
|
|
|
-- Print key
|
|
local sKey = kName or keyStr[key]
|
|
|
|
gMsgC(kName and color_reference or color_name)
|
|
gMsgF(sKey)
|
|
|
|
-- Describe non primitives
|
|
describe = istable(value) and ( not names[value] or todo[value] ) and value ~= NIL
|
|
|
|
-- Print key postfix
|
|
local padding = keyLen - string.len(sKey)
|
|
local postfix = string.format(describe and ":%s" or "%s = ", string.rep(' ', padding))
|
|
|
|
gMsgC(color_neutral)
|
|
gMsgF(postfix)
|
|
|
|
-- Print the value
|
|
if describe then
|
|
gMsgN()
|
|
|
|
-- Expand access path
|
|
local new_path = sKey
|
|
|
|
if isnumber(key) or kName then
|
|
new_path = string.format("%s[%s]", path or '', key)
|
|
elseif path then
|
|
new_path = string.format("%s.%s", path, new_path)
|
|
end
|
|
|
|
-- Name the object to mark it done
|
|
names[value] = names[value] or new_path
|
|
|
|
-- Describe
|
|
InternalPrintTable(value, new_path, new_prefix, names, todo)
|
|
else
|
|
-- Print the value (or the reference name)
|
|
if vName and not todo[value] then
|
|
gMsgC(color_reference)
|
|
gMsgF(string.format("ref: %s",vName))
|
|
else
|
|
InternalPrintValue(value)
|
|
end
|
|
|
|
gMsgN()
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
function PrintTableGrep( table, grep, proximity )
|
|
local base = {
|
|
[_G] = "_G",
|
|
[table] = "root"
|
|
}
|
|
|
|
gBegin(grep, proximity)
|
|
objID = 0
|
|
InternalPrintTable(table, nil, "", base, {})
|
|
gFinish()
|
|
end
|
|
|
|
function PrintLocals( level )
|
|
local level = level or 2
|
|
local hash = {}
|
|
|
|
for index = 1, 255 do
|
|
local name, value = debug.getlocal(2, index)
|
|
|
|
if not name then
|
|
break
|
|
end
|
|
|
|
if value == nil then
|
|
value = NIL
|
|
end
|
|
|
|
hash[name] = value
|
|
end
|
|
|
|
PrintTableGrep( hash )
|
|
end
|
|
|
|
function show(...)
|
|
local n = select('#', ...)
|
|
local tbl = {...}
|
|
|
|
for i = 1, n do
|
|
if istable(tbl[i]) then MsgN(tostring(tbl[i])) PrintTableGrep(tbl[i])
|
|
else InternalPrintValue(tbl[i]) MsgN() end
|
|
end
|
|
end
|
|
|
|
-- Hacky way of creating a pretty string from the above code
|
|
-- because I don't feel like refactoring the entire thing
|
|
local strResult
|
|
local toStringMsgF = function(txt)
|
|
table.insert(strResult, txt)
|
|
end
|
|
|
|
local toStringMsgN = function()
|
|
table.insert(strResult, "\n")
|
|
end
|
|
|
|
local toStringMsgC = function(_, txt)
|
|
table.insert(strResult, txt)
|
|
end
|
|
|
|
function showStr(...)
|
|
local oldF, oldN, oldMsgC, oldMsgN = gMsgF, gMsgN, MsgC, MsgN
|
|
gMsgF, gMsgN, MsgC, MsgN = toStringMsgF, toStringMsgN, toStringMsgC, toStringMsgN
|
|
|
|
strResult = {}
|
|
show(...)
|
|
|
|
gMsgF, gMsgN, MsgC, MsgN = oldF, oldN, oldMsgC, oldMsgN
|
|
|
|
return table.concat(strResult, "")
|
|
end
|