Initial commit
This commit is contained in:
809
lib/lain/util/dkjson.lua
Normal file
809
lib/lain/util/dkjson.lua
Normal file
@@ -0,0 +1,809 @@
|
||||
-- Module options:
|
||||
local always_use_lpeg = false
|
||||
local register_global_module_table = false
|
||||
local global_module_name = 'json'
|
||||
|
||||
--[==[
|
||||
|
||||
David Kolf's JSON module for Lua 5.1 - 5.4
|
||||
|
||||
Version 2.6
|
||||
|
||||
|
||||
For the documentation see the corresponding readme.txt or visit
|
||||
<http://dkolf.de/src/dkjson-lua.fsl/>.
|
||||
|
||||
You can contact the author by sending an e-mail to 'david' at the
|
||||
domain 'dkolf.de'.
|
||||
|
||||
|
||||
Copyright (C) 2010-2021 David Heiko Kolf
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
--]==]
|
||||
|
||||
-- global dependencies:
|
||||
local pairs, type, tostring, tonumber, getmetatable, setmetatable =
|
||||
pairs, type, tostring, tonumber, getmetatable, setmetatable
|
||||
local error, require, pcall, select = error, require, pcall, select
|
||||
local floor, huge = math.floor, math.huge
|
||||
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
|
||||
string.rep, string.gsub, string.sub, string.byte, string.char, string.find, string.len, string.format
|
||||
local strmatch = string.match
|
||||
local concat = table.concat
|
||||
|
||||
local json = { version = 'dkjson 2.6' }
|
||||
|
||||
local jsonlpeg = {}
|
||||
|
||||
if register_global_module_table then
|
||||
if always_use_lpeg then
|
||||
_G[global_module_name] = jsonlpeg
|
||||
else
|
||||
_G[global_module_name] = json
|
||||
end
|
||||
end
|
||||
|
||||
_ENV = nil -- blocking globals in Lua 5.2 and later
|
||||
|
||||
pcall(function()
|
||||
-- Enable access to blocked metatables.
|
||||
-- Don't worry, this module doesn't change anything in them.
|
||||
local debmeta = require('debug').getmetatable
|
||||
if debmeta then
|
||||
getmetatable = debmeta
|
||||
end
|
||||
end)
|
||||
|
||||
json.null = setmetatable({}, {
|
||||
__tojson = function()
|
||||
return 'null'
|
||||
end,
|
||||
})
|
||||
|
||||
local function isarray(tbl)
|
||||
local max, n, arraylen = 0, 0, 0
|
||||
for k, v in pairs(tbl) do
|
||||
if k == 'n' and type(v) == 'number' then
|
||||
arraylen = v
|
||||
if v > max then
|
||||
max = v
|
||||
end
|
||||
else
|
||||
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
|
||||
return false
|
||||
end
|
||||
if k > max then
|
||||
max = k
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
if max > 10 and max > arraylen and max > n * 2 then
|
||||
return false -- don't create an array with too many holes
|
||||
end
|
||||
return true, max
|
||||
end
|
||||
|
||||
local escapecodes = {
|
||||
['"'] = '\\"',
|
||||
['\\'] = '\\\\',
|
||||
['\b'] = '\\b',
|
||||
['\f'] = '\\f',
|
||||
['\n'] = '\\n',
|
||||
['\r'] = '\\r',
|
||||
['\t'] = '\\t',
|
||||
}
|
||||
|
||||
local function escapeutf8(uchar)
|
||||
local value = escapecodes[uchar]
|
||||
if value then
|
||||
return value
|
||||
end
|
||||
local a, b, c, d = strbyte(uchar, 1, 4)
|
||||
a, b, c, d = a or 0, b or 0, c or 0, d or 0
|
||||
if a <= 0x7f then
|
||||
value = a
|
||||
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
|
||||
value = (a - 0xc0) * 0x40 + b - 0x80
|
||||
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
|
||||
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
|
||||
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
|
||||
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
|
||||
else
|
||||
return ''
|
||||
end
|
||||
if value <= 0xffff then
|
||||
return strformat('\\u%.4x', value)
|
||||
elseif value <= 0x10ffff then
|
||||
-- encode as UTF-16 surrogate pair
|
||||
value = value - 0x10000
|
||||
local highsur, lowsur = 0xD800 + floor(value / 0x400), 0xDC00 + (value % 0x400)
|
||||
return strformat('\\u%.4x\\u%.4x', highsur, lowsur)
|
||||
else
|
||||
return ''
|
||||
end
|
||||
end
|
||||
|
||||
local function fsub(str, pattern, repl)
|
||||
-- gsub always builds a new string in a buffer, even when no match
|
||||
-- exists. First using find should be more efficient when most strings
|
||||
-- don't contain the pattern.
|
||||
if strfind(str, pattern) then
|
||||
return gsub(str, pattern, repl)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
local function quotestring(value)
|
||||
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
|
||||
value = fsub(value, '[%z\1-\31"\\\127]', escapeutf8)
|
||||
if strfind(value, '[\194\216\220\225\226\239]') then
|
||||
value = fsub(value, '\194[\128-\159\173]', escapeutf8)
|
||||
value = fsub(value, '\216[\128-\132]', escapeutf8)
|
||||
value = fsub(value, '\220\143', escapeutf8)
|
||||
value = fsub(value, '\225\158[\180\181]', escapeutf8)
|
||||
value = fsub(value, '\226\128[\140-\143\168-\175]', escapeutf8)
|
||||
value = fsub(value, '\226\129[\160-\175]', escapeutf8)
|
||||
value = fsub(value, '\239\187\191', escapeutf8)
|
||||
value = fsub(value, '\239\191[\176-\191]', escapeutf8)
|
||||
end
|
||||
return '"' .. value .. '"'
|
||||
end
|
||||
json.quotestring = quotestring
|
||||
|
||||
local function replace(str, o, n)
|
||||
local i, j = strfind(str, o, 1, true)
|
||||
if i then
|
||||
return strsub(str, 1, i - 1) .. n .. strsub(str, j + 1, -1)
|
||||
else
|
||||
return str
|
||||
end
|
||||
end
|
||||
|
||||
-- locale independent num2str and str2num functions
|
||||
local decpoint, numfilter
|
||||
|
||||
local function updatedecpoint()
|
||||
decpoint = strmatch(tostring(0.5), '([^05+])')
|
||||
-- build a filter that can be used to remove group separators
|
||||
numfilter = '[^0-9%-%+eE' .. gsub(decpoint, '[%^%$%(%)%%%.%[%]%*%+%-%?]', '%%%0') .. ']+'
|
||||
end
|
||||
|
||||
updatedecpoint()
|
||||
|
||||
local function num2str(num)
|
||||
return replace(fsub(tostring(num), numfilter, ''), decpoint, '.')
|
||||
end
|
||||
|
||||
local function str2num(str)
|
||||
local num = tonumber(replace(str, '.', decpoint))
|
||||
if not num then
|
||||
updatedecpoint()
|
||||
num = tonumber(replace(str, '.', decpoint))
|
||||
end
|
||||
return num
|
||||
end
|
||||
|
||||
local function addnewline2(level, buffer, buflen)
|
||||
buffer[buflen + 1] = '\n'
|
||||
buffer[buflen + 2] = strrep(' ', level)
|
||||
buflen = buflen + 2
|
||||
return buflen
|
||||
end
|
||||
|
||||
function json.addnewline(state)
|
||||
if state.indent then
|
||||
state.bufferlen = addnewline2(state.level or 0, state.buffer, state.bufferlen or #state.buffer)
|
||||
end
|
||||
end
|
||||
|
||||
local encode2 -- forward declaration
|
||||
|
||||
local function addpair(key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
local kt = type(key)
|
||||
if kt ~= 'string' and kt ~= 'number' then
|
||||
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
|
||||
end
|
||||
if prev then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ','
|
||||
end
|
||||
if indent then
|
||||
buflen = addnewline2(level, buffer, buflen)
|
||||
end
|
||||
buffer[buflen + 1] = quotestring(key)
|
||||
buffer[buflen + 2] = ':'
|
||||
return encode2(value, indent, level, buffer, buflen + 2, tables, globalorder, state)
|
||||
end
|
||||
|
||||
local function appendcustom(res, buffer, state)
|
||||
local buflen = state.bufferlen
|
||||
if type(res) == 'string' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = res
|
||||
end
|
||||
return buflen
|
||||
end
|
||||
|
||||
local function exception(reason, value, state, buffer, buflen, defaultmessage)
|
||||
defaultmessage = defaultmessage or reason
|
||||
local handler = state.exception
|
||||
if not handler then
|
||||
return nil, defaultmessage
|
||||
else
|
||||
state.bufferlen = buflen
|
||||
local ret, msg = handler(reason, value, state, defaultmessage)
|
||||
if not ret then
|
||||
return nil, msg or defaultmessage
|
||||
end
|
||||
return appendcustom(ret, buffer, state)
|
||||
end
|
||||
end
|
||||
|
||||
function json.encodeexception(_reason, _value, _state, defaultmessage)
|
||||
return quotestring('<' .. defaultmessage .. '>')
|
||||
end
|
||||
|
||||
encode2 = function(value, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
local valtype = type(value)
|
||||
local valmeta = getmetatable(value)
|
||||
valmeta = type(valmeta) == 'table' and valmeta -- only tables
|
||||
local valtojson = valmeta and valmeta.__tojson
|
||||
if valtojson then
|
||||
if tables[value] then
|
||||
return exception('reference cycle', value, state, buffer, buflen)
|
||||
end
|
||||
tables[value] = true
|
||||
state.bufferlen = buflen
|
||||
local ret, msg = valtojson(value, state)
|
||||
if not ret then
|
||||
return exception('custom encoder failed', value, state, buffer, buflen, msg)
|
||||
end
|
||||
tables[value] = nil
|
||||
buflen = appendcustom(ret, buffer, state)
|
||||
elseif value == nil then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = 'null'
|
||||
elseif valtype == 'number' then
|
||||
local s
|
||||
if value ~= value or value >= huge or -value >= huge then
|
||||
-- This is the behaviour of the original JSON implementation.
|
||||
s = 'null'
|
||||
else
|
||||
s = num2str(value)
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = s
|
||||
elseif valtype == 'boolean' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = value and 'true' or 'false'
|
||||
elseif valtype == 'string' then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = quotestring(value)
|
||||
elseif valtype == 'table' then
|
||||
if tables[value] then
|
||||
return exception('reference cycle', value, state, buffer, buflen)
|
||||
end
|
||||
tables[value] = true
|
||||
level = level + 1
|
||||
local isa, n = isarray(value)
|
||||
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
|
||||
isa = false
|
||||
end
|
||||
local msg
|
||||
if isa then -- JSON array
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = '['
|
||||
for i = 1, n do
|
||||
buflen, msg = encode2(value[i], indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then
|
||||
return nil, msg
|
||||
end
|
||||
if i < n then
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ','
|
||||
end
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = ']'
|
||||
else -- JSON object
|
||||
local prev = false
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = '{'
|
||||
local order = valmeta and valmeta.__jsonorder or globalorder
|
||||
if order then
|
||||
local used = {}
|
||||
n = #order
|
||||
for i = 1, n do
|
||||
local k = order[i]
|
||||
local v = value[k]
|
||||
if v ~= nil then
|
||||
used[k] = true
|
||||
buflen, _msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
for k, v in pairs(value) do
|
||||
if not used[k] then
|
||||
buflen, msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then
|
||||
return nil, msg
|
||||
end
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
else -- unordered
|
||||
for k, v in pairs(value) do
|
||||
buflen, msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
|
||||
if not buflen then
|
||||
return nil, msg
|
||||
end
|
||||
prev = true -- add a seperator before the next element
|
||||
end
|
||||
end
|
||||
if indent then
|
||||
buflen = addnewline2(level - 1, buffer, buflen)
|
||||
end
|
||||
buflen = buflen + 1
|
||||
buffer[buflen] = '}'
|
||||
end
|
||||
tables[value] = nil
|
||||
else
|
||||
return exception(
|
||||
'unsupported type',
|
||||
value,
|
||||
state,
|
||||
buffer,
|
||||
buflen,
|
||||
"type '" .. valtype .. "' is not supported by JSON."
|
||||
)
|
||||
end
|
||||
return buflen
|
||||
end
|
||||
|
||||
function json.encode(value, state)
|
||||
state = state or {}
|
||||
local oldbuffer = state.buffer
|
||||
local buffer = oldbuffer or {}
|
||||
state.buffer = buffer
|
||||
updatedecpoint()
|
||||
local ret, msg = encode2(
|
||||
value,
|
||||
state.indent,
|
||||
state.level or 0,
|
||||
buffer,
|
||||
state.bufferlen or 0,
|
||||
state.tables or {},
|
||||
state.keyorder,
|
||||
state
|
||||
)
|
||||
if not ret then
|
||||
error(msg, 2)
|
||||
elseif oldbuffer == buffer then
|
||||
state.bufferlen = ret
|
||||
return true
|
||||
else
|
||||
state.bufferlen = nil
|
||||
state.buffer = nil
|
||||
return concat(buffer)
|
||||
end
|
||||
end
|
||||
|
||||
local function loc(str, where)
|
||||
local line, pos, linepos = 1, 1, 0
|
||||
while true do
|
||||
pos = strfind(str, '\n', pos, true)
|
||||
if pos and pos < where then
|
||||
line = line + 1
|
||||
linepos = pos
|
||||
pos = pos + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return 'line ' .. line .. ', column ' .. (where - linepos)
|
||||
end
|
||||
|
||||
local function unterminated(str, what, where)
|
||||
return nil, strlen(str) + 1, 'unterminated ' .. what .. ' at ' .. loc(str, where)
|
||||
end
|
||||
|
||||
local function scanwhite(str, pos)
|
||||
while true do
|
||||
pos = strfind(str, '%S', pos)
|
||||
if not pos then
|
||||
return nil
|
||||
end
|
||||
local sub2 = strsub(str, pos, pos + 1)
|
||||
if sub2 == '\239\187' and strsub(str, pos + 2, pos + 2) == '\191' then
|
||||
-- UTF-8 Byte Order Mark
|
||||
pos = pos + 3
|
||||
elseif sub2 == '//' then
|
||||
pos = strfind(str, '[\n\r]', pos + 2)
|
||||
if not pos then
|
||||
return nil
|
||||
end
|
||||
elseif sub2 == '/*' then
|
||||
pos = strfind(str, '*/', pos + 2)
|
||||
if not pos then
|
||||
return nil
|
||||
end
|
||||
pos = pos + 2
|
||||
else
|
||||
return pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local escapechars = {
|
||||
['"'] = '"',
|
||||
['\\'] = '\\',
|
||||
['/'] = '/',
|
||||
['b'] = '\b',
|
||||
['f'] = '\f',
|
||||
['n'] = '\n',
|
||||
['r'] = '\r',
|
||||
['t'] = '\t',
|
||||
}
|
||||
|
||||
local function unichar(value)
|
||||
if value < 0 then
|
||||
return nil
|
||||
elseif value <= 0x007f then
|
||||
return strchar(value)
|
||||
elseif value <= 0x07ff then
|
||||
return strchar(0xc0 + floor(value / 0x40), 0x80 + (floor(value) % 0x40))
|
||||
elseif value <= 0xffff then
|
||||
return strchar(0xe0 + floor(value / 0x1000), 0x80 + (floor(value / 0x40) % 0x40), 0x80 + (floor(value) % 0x40))
|
||||
elseif value <= 0x10ffff then
|
||||
return strchar(
|
||||
0xf0 + floor(value / 0x40000),
|
||||
0x80 + (floor(value / 0x1000) % 0x40),
|
||||
0x80 + (floor(value / 0x40) % 0x40),
|
||||
0x80 + (floor(value) % 0x40)
|
||||
)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
local function scanstring(str, pos)
|
||||
local lastpos = pos + 1
|
||||
local buffer, n = {}, 0
|
||||
while true do
|
||||
local nextpos = strfind(str, '["\\]', lastpos)
|
||||
if not nextpos then
|
||||
return unterminated(str, 'string', pos)
|
||||
end
|
||||
if nextpos > lastpos then
|
||||
n = n + 1
|
||||
buffer[n] = strsub(str, lastpos, nextpos - 1)
|
||||
end
|
||||
if strsub(str, nextpos, nextpos) == '"' then
|
||||
lastpos = nextpos + 1
|
||||
break
|
||||
else
|
||||
local escchar = strsub(str, nextpos + 1, nextpos + 1)
|
||||
local value
|
||||
if escchar == 'u' then
|
||||
value = tonumber(strsub(str, nextpos + 2, nextpos + 5), 16)
|
||||
if value then
|
||||
local value2
|
||||
if 0xD800 <= value and value <= 0xDBff then
|
||||
-- we have the high surrogate of UTF-16. Check if there is a
|
||||
-- low surrogate escaped nearby to combine them.
|
||||
if strsub(str, nextpos + 6, nextpos + 7) == '\\u' then
|
||||
value2 = tonumber(strsub(str, nextpos + 8, nextpos + 11), 16)
|
||||
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
|
||||
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
|
||||
else
|
||||
value2 = nil -- in case it was out of range for a low surrogate
|
||||
end
|
||||
end
|
||||
end
|
||||
value = value and unichar(value)
|
||||
if value then
|
||||
if value2 then
|
||||
lastpos = nextpos + 12
|
||||
else
|
||||
lastpos = nextpos + 6
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if not value then
|
||||
value = escapechars[escchar] or escchar
|
||||
lastpos = nextpos + 2
|
||||
end
|
||||
n = n + 1
|
||||
buffer[n] = value
|
||||
end
|
||||
end
|
||||
if n == 1 then
|
||||
return buffer[1], lastpos
|
||||
elseif n > 1 then
|
||||
return concat(buffer), lastpos
|
||||
else
|
||||
return '', lastpos
|
||||
end
|
||||
end
|
||||
|
||||
local scanvalue -- forward declaration
|
||||
|
||||
local function scantable(what, closechar, str, startpos, nullval, objectmeta, arraymeta)
|
||||
local tbl, n = {}, 0
|
||||
local pos = startpos + 1
|
||||
if what == 'object' then
|
||||
setmetatable(tbl, objectmeta)
|
||||
else
|
||||
setmetatable(tbl, arraymeta)
|
||||
end
|
||||
while true do
|
||||
pos = scanwhite(str, pos)
|
||||
if not pos then
|
||||
return unterminated(str, what, startpos)
|
||||
end
|
||||
local char = strsub(str, pos, pos)
|
||||
if char == closechar then
|
||||
return tbl, pos + 1
|
||||
end
|
||||
local val1, err
|
||||
val1, pos, err = scanvalue(str, pos, nullval, objectmeta, arraymeta)
|
||||
if err then
|
||||
return nil, pos, err
|
||||
end
|
||||
pos = scanwhite(str, pos)
|
||||
if not pos then
|
||||
return unterminated(str, what, startpos)
|
||||
end
|
||||
char = strsub(str, pos, pos)
|
||||
if char == ':' then
|
||||
if val1 == nil then
|
||||
return nil, pos, 'cannot use nil as table index (at ' .. loc(str, pos) .. ')'
|
||||
end
|
||||
pos = scanwhite(str, pos + 1)
|
||||
if not pos then
|
||||
return unterminated(str, what, startpos)
|
||||
end
|
||||
local val2
|
||||
val2, pos, err = scanvalue(str, pos, nullval, objectmeta, arraymeta)
|
||||
if err then
|
||||
return nil, pos, err
|
||||
end
|
||||
tbl[val1] = val2
|
||||
pos = scanwhite(str, pos)
|
||||
if not pos then
|
||||
return unterminated(str, what, startpos)
|
||||
end
|
||||
char = strsub(str, pos, pos)
|
||||
else
|
||||
n = n + 1
|
||||
tbl[n] = val1
|
||||
end
|
||||
if char == ',' then
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
scanvalue = function(str, pos, nullval, objectmeta, arraymeta)
|
||||
pos = pos or 1
|
||||
pos = scanwhite(str, pos)
|
||||
if not pos then
|
||||
return nil, strlen(str) + 1, 'no valid JSON value (reached the end)'
|
||||
end
|
||||
local char = strsub(str, pos, pos)
|
||||
if char == '{' then
|
||||
return scantable('object', '}', str, pos, nullval, objectmeta, arraymeta)
|
||||
elseif char == '[' then
|
||||
return scantable('array', ']', str, pos, nullval, objectmeta, arraymeta)
|
||||
elseif char == '"' then
|
||||
return scanstring(str, pos)
|
||||
else
|
||||
local pstart, pend = strfind(str, '^%-?[%d%.]+[eE]?[%+%-]?%d*', pos)
|
||||
if pstart then
|
||||
local number = str2num(strsub(str, pstart, pend))
|
||||
if number then
|
||||
return number, pend + 1
|
||||
end
|
||||
end
|
||||
pstart, pend = strfind(str, '^%a%w*', pos)
|
||||
if pstart then
|
||||
local name = strsub(str, pstart, pend)
|
||||
if name == 'true' then
|
||||
return true, pend + 1
|
||||
elseif name == 'false' then
|
||||
return false, pend + 1
|
||||
elseif name == 'null' then
|
||||
return nullval, pend + 1
|
||||
end
|
||||
end
|
||||
return nil, pos, 'no valid JSON value at ' .. loc(str, pos)
|
||||
end
|
||||
end
|
||||
|
||||
local function optionalmetatables(...)
|
||||
if select('#', ...) > 0 then
|
||||
return ...
|
||||
else
|
||||
return { __jsontype = 'object' }, { __jsontype = 'array' }
|
||||
end
|
||||
end
|
||||
|
||||
function json.decode(str, pos, nullval, ...)
|
||||
local objectmeta, arraymeta = optionalmetatables(...)
|
||||
return scanvalue(str, pos, nullval, objectmeta, arraymeta)
|
||||
end
|
||||
|
||||
function json.use_lpeg()
|
||||
local g = require('lpeg')
|
||||
|
||||
if g.version() == '0.11' then
|
||||
error('due to a bug in LPeg 0.11, it cannot be used for JSON matching')
|
||||
end
|
||||
|
||||
local pegmatch = g.match
|
||||
local P, S, R = g.P, g.S, g.R
|
||||
|
||||
local function ErrorCall(str, pos, msg, state)
|
||||
if not state.msg then
|
||||
state.msg = msg .. ' at ' .. loc(str, pos)
|
||||
state.pos = pos
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function Err(msg)
|
||||
return g.Cmt(g.Cc(msg) * g.Carg(2), ErrorCall)
|
||||
end
|
||||
|
||||
local function ErrorUnterminatedCall(str, pos, what, state)
|
||||
return ErrorCall(str, pos - 1, 'unterminated ' .. what, state)
|
||||
end
|
||||
|
||||
local SingleLineComment = P('//') * (1 - S('\n\r')) ^ 0
|
||||
local MultiLineComment = P('/*') * (1 - P('*/')) ^ 0 * P('*/')
|
||||
local Space = (S(' \n\r\t') + P('\239\187\191') + SingleLineComment + MultiLineComment) ^ 0
|
||||
|
||||
local function ErrUnterminated(what)
|
||||
return g.Cmt(g.Cc(what) * g.Carg(2), ErrorUnterminatedCall)
|
||||
end
|
||||
|
||||
local PlainChar = 1 - S('"\\\n\r')
|
||||
local EscapeSequence = (P('\\') * g.C(S('"\\/bfnrt') + Err('unsupported escape sequence'))) / escapechars
|
||||
local HexDigit = R('09', 'af', 'AF')
|
||||
local function UTF16Surrogate(_match, _pos, high, low)
|
||||
high, low = tonumber(high, 16), tonumber(low, 16)
|
||||
if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
|
||||
return true, unichar((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
local function UTF16BMP(hex)
|
||||
return unichar(tonumber(hex, 16))
|
||||
end
|
||||
local U16Sequence = (P('\\u') * g.C(HexDigit * HexDigit * HexDigit * HexDigit))
|
||||
local UnicodeEscape = g.Cmt(U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence / UTF16BMP
|
||||
local Char = UnicodeEscape + EscapeSequence + PlainChar
|
||||
local String = P('"') * (g.Cs(Char ^ 0) * P('"') + ErrUnterminated('string'))
|
||||
local Integer = P('-') ^ -1 * (P('0') + (R('19') * R('09') ^ 0))
|
||||
local Fractal = P('.') * R('09') ^ 0
|
||||
local Exponent = (S('eE')) * (S('+-')) ^ -1 * R('09') ^ 1
|
||||
local Number = (Integer * Fractal ^ -1 * Exponent ^ -1) / str2num
|
||||
local Constant = P('true') * g.Cc(true) + P('false') * g.Cc(false) + P('null') * g.Carg(1)
|
||||
local SimpleValue = Number + String + Constant
|
||||
local ArrayContent, ObjectContent
|
||||
|
||||
-- The functions parsearray and parseobject parse only a single value/pair
|
||||
-- at a time and store them directly to avoid hitting the LPeg limits.
|
||||
local function parsearray(str, pos, nullval, state)
|
||||
local obj, cont
|
||||
local start = pos
|
||||
local npos
|
||||
local t, nt = {}, 0
|
||||
repeat
|
||||
obj, cont, npos = pegmatch(ArrayContent, str, pos, nullval, state)
|
||||
if cont == 'end' then
|
||||
return ErrorUnterminatedCall(str, start, 'array', state)
|
||||
end
|
||||
pos = npos
|
||||
if cont == 'cont' or cont == 'last' then
|
||||
nt = nt + 1
|
||||
t[nt] = obj
|
||||
end
|
||||
until cont ~= 'cont'
|
||||
return pos, setmetatable(t, state.arraymeta)
|
||||
end
|
||||
|
||||
local function parseobject(str, pos, nullval, state)
|
||||
local obj, key, cont
|
||||
local start = pos
|
||||
local npos
|
||||
local t = {}
|
||||
repeat
|
||||
key, obj, cont, npos = pegmatch(ObjectContent, str, pos, nullval, state)
|
||||
if cont == 'end' then
|
||||
return ErrorUnterminatedCall(str, start, 'object', state)
|
||||
end
|
||||
pos = npos
|
||||
if cont == 'cont' or cont == 'last' then
|
||||
t[key] = obj
|
||||
end
|
||||
until cont ~= 'cont'
|
||||
return pos, setmetatable(t, state.objectmeta)
|
||||
end
|
||||
|
||||
local Array = P('[') * g.Cmt(g.Carg(1) * g.Carg(2), parsearray)
|
||||
local Object = P('{') * g.Cmt(g.Carg(1) * g.Carg(2), parseobject)
|
||||
local Value = Space * (Array + Object + SimpleValue)
|
||||
local ExpectedValue = Value + Space * Err('value expected')
|
||||
local ExpectedKey = String + Err('key expected')
|
||||
local End = P(-1) * g.Cc('end')
|
||||
local ErrInvalid = Err('invalid JSON')
|
||||
ArrayContent = (
|
||||
Value * Space * (P(',') * g.Cc('cont') + P(']') * g.Cc('last') + End + ErrInvalid)
|
||||
+ g.Cc(nil) * (P(']') * g.Cc('empty') + End + ErrInvalid)
|
||||
) * g.Cp()
|
||||
local Pair = g.Cg(Space * ExpectedKey * Space * (P(':') + Err('colon expected')) * ExpectedValue)
|
||||
ObjectContent = (
|
||||
g.Cc(nil) * g.Cc(nil) * P('}') * g.Cc('empty')
|
||||
+ End
|
||||
+ (Pair * Space * (P(',') * g.Cc('cont') + P('}') * g.Cc('last') + End + ErrInvalid) + ErrInvalid)
|
||||
) * g.Cp()
|
||||
local DecodeValue = ExpectedValue * g.Cp()
|
||||
|
||||
jsonlpeg.version = json.version
|
||||
jsonlpeg.encode = json.encode
|
||||
jsonlpeg.null = json.null
|
||||
jsonlpeg.quotestring = json.quotestring
|
||||
jsonlpeg.addnewline = json.addnewline
|
||||
jsonlpeg.encodeexception = json.encodeexception
|
||||
jsonlpeg.using_lpeg = true
|
||||
|
||||
function jsonlpeg.decode(str, pos, nullval, ...)
|
||||
local state = {}
|
||||
state.objectmeta, state.arraymeta = optionalmetatables(...)
|
||||
local obj, retpos = pegmatch(DecodeValue, str, pos, nullval, state)
|
||||
if state.msg then
|
||||
return nil, state.pos, state.msg
|
||||
else
|
||||
return obj, retpos
|
||||
end
|
||||
end
|
||||
|
||||
-- cache result of this function:
|
||||
json.use_lpeg = function()
|
||||
return jsonlpeg
|
||||
end
|
||||
jsonlpeg.use_lpeg = json.use_lpeg
|
||||
|
||||
return jsonlpeg
|
||||
end
|
||||
|
||||
if always_use_lpeg then
|
||||
return json.use_lpeg()
|
||||
end
|
||||
|
||||
return json
|
||||
210
lib/lain/util/init.lua
Normal file
210
lib/lain/util/init.lua
Normal file
@@ -0,0 +1,210 @@
|
||||
--[[
|
||||
|
||||
Lain
|
||||
Layouts, widgets and utilities for Awesome WM
|
||||
|
||||
Utilities section
|
||||
|
||||
Licensed under GNU General Public License v2
|
||||
* (c) 2013, Luca CPZ
|
||||
* (c) 2010-2012, Peter Hofmann
|
||||
|
||||
--]]
|
||||
|
||||
local awful = require('awful')
|
||||
local sqrt = math.sqrt
|
||||
local pairs = pairs
|
||||
local client = client
|
||||
local tonumber = tonumber
|
||||
local wrequire = require('lain.helpers').wrequire
|
||||
local setmetatable = setmetatable
|
||||
|
||||
-- Lain utilities submodule
|
||||
-- lain.util
|
||||
local util = { _NAME = 'lain.util' }
|
||||
|
||||
-- Like awful.menu.clients, but only show clients of currently selected tags
|
||||
function util.menu_clients_current_tags(menu, args)
|
||||
-- List of currently selected tags.
|
||||
local cls_tags = awful.screen.focused().selected_tags
|
||||
|
||||
if cls_tags == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Final list of menu items.
|
||||
local cls_t = {}
|
||||
|
||||
-- For each selected tag get all clients of that tag and add them to
|
||||
-- the menu. A click on a menu item will raise that client.
|
||||
for i = 1, #cls_tags do
|
||||
local t = cls_tags[i]
|
||||
local cls = t:clients()
|
||||
|
||||
for _, c in pairs(cls) do
|
||||
cls_t[#cls_t + 1] = {
|
||||
awful.util.escape(c.name) or '',
|
||||
function()
|
||||
c.minimized = false
|
||||
client.focus = c
|
||||
c:raise()
|
||||
end,
|
||||
c.icon,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- No clients? Then quit.
|
||||
if #cls_t <= 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- menu may contain some predefined values, otherwise start with a
|
||||
-- fresh menu.
|
||||
if not menu then
|
||||
menu = {}
|
||||
end
|
||||
|
||||
-- Set the list of items and show the menu.
|
||||
menu.items = cls_t
|
||||
local m = awful.menu(menu)
|
||||
m:show(args)
|
||||
|
||||
return m
|
||||
end
|
||||
|
||||
-- Magnify a client: set it to "float" and resize it.
|
||||
function util.magnify_client(c, width_f, height_f)
|
||||
if c and not c.floating then
|
||||
util.magnified_client = c
|
||||
util.mc(c, width_f, height_f)
|
||||
else
|
||||
util.magnified_client = nil
|
||||
c.floating = false
|
||||
end
|
||||
end
|
||||
|
||||
-- https://github.com/lcpz/lain/issues/195
|
||||
function util.mc(c, width_f, height_f)
|
||||
c = c or util.magnified_client
|
||||
if not c then
|
||||
return
|
||||
end
|
||||
|
||||
c.floating = true
|
||||
local s = awful.screen.focused()
|
||||
local mg = s.workarea
|
||||
local g = {}
|
||||
local mwfact = width_f or s.selected_tag.master_width_factor or 0.5
|
||||
g.width = sqrt(mwfact) * mg.width
|
||||
g.height = sqrt(height_f or mwfact) * mg.height
|
||||
g.x = mg.x + (mg.width - g.width) / 2
|
||||
g.y = mg.y + (mg.height - g.height) / 2
|
||||
|
||||
if c then
|
||||
c:geometry(g)
|
||||
end -- if c is still a valid object
|
||||
end
|
||||
|
||||
-- Non-empty tag browsing
|
||||
-- direction in {-1, 1} <-> {previous, next} non-empty tag
|
||||
function util.tag_view_nonempty(direction, sc)
|
||||
direction = direction or 1
|
||||
local s = sc or awful.screen.focused()
|
||||
local tags = s.tags
|
||||
local sel = s.selected_tag
|
||||
|
||||
local i = sel.index
|
||||
repeat
|
||||
i = i + direction
|
||||
|
||||
-- Wrap around when we reach one of the bounds
|
||||
if i > #tags then
|
||||
i = i - #tags
|
||||
end
|
||||
if i < 1 then
|
||||
i = i + #tags
|
||||
end
|
||||
|
||||
local t = tags[i]
|
||||
|
||||
-- Stop when we get back to where we started
|
||||
if t == sel then
|
||||
break
|
||||
end
|
||||
|
||||
-- If it's The One, view it.
|
||||
if #t:clients() > 0 then
|
||||
t:view_only()
|
||||
return
|
||||
end
|
||||
until false
|
||||
end
|
||||
|
||||
-- {{{ Dynamic tagging
|
||||
|
||||
-- Add a new tag
|
||||
function util.add_tag(layout)
|
||||
awful.prompt.run({
|
||||
prompt = 'New tag name: ',
|
||||
textbox = awful.screen.focused().mypromptbox.widget,
|
||||
exe_callback = function(name)
|
||||
if not name or #name == 0 then
|
||||
return
|
||||
end
|
||||
awful.tag
|
||||
.add(name, { screen = awful.screen.focused(), layout = layout or awful.layout.suit.tile })
|
||||
:view_only()
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
-- Rename current tag
|
||||
function util.rename_tag()
|
||||
awful.prompt.run({
|
||||
prompt = 'Rename tag: ',
|
||||
textbox = awful.screen.focused().mypromptbox.widget,
|
||||
exe_callback = function(new_name)
|
||||
if not new_name or #new_name == 0 then
|
||||
return
|
||||
end
|
||||
local t = awful.screen.focused().selected_tag
|
||||
if t then
|
||||
t.name = new_name
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
-- Move current tag
|
||||
-- pos in {-1, 1} <-> {previous, next} tag position
|
||||
function util.move_tag(pos)
|
||||
local tag = awful.screen.focused().selected_tag
|
||||
if tonumber(pos) <= -1 then
|
||||
awful.tag.move(tag.index - 1, tag)
|
||||
else
|
||||
awful.tag.move(tag.index + 1, tag)
|
||||
end
|
||||
end
|
||||
|
||||
-- Delete current tag
|
||||
-- Any rule set on the tag shall be broken
|
||||
function util.delete_tag()
|
||||
local t = awful.screen.focused().selected_tag
|
||||
if not t then
|
||||
return
|
||||
end
|
||||
t:delete()
|
||||
end
|
||||
|
||||
-- }}}
|
||||
|
||||
-- On the fly useless gaps change
|
||||
function util.useless_gaps_resize(thatmuch, s, t)
|
||||
local scr = s or awful.screen.focused()
|
||||
local tag = t or scr.selected_tag
|
||||
tag.gap = tag.gap + tonumber(thatmuch)
|
||||
awful.layout.arrange(scr)
|
||||
end
|
||||
|
||||
return setmetatable(util, { __index = wrequire })
|
||||
92
lib/lain/util/markup.lua
Normal file
92
lib/lain/util/markup.lua
Normal file
@@ -0,0 +1,92 @@
|
||||
--[[
|
||||
|
||||
Licensed under MIT License
|
||||
* (c) 2013, Luca CPZ
|
||||
* (c) 2009, Uli Schlachter
|
||||
* (c) 2009, Majic
|
||||
|
||||
--]]
|
||||
|
||||
local format = string.format
|
||||
local setmetatable = setmetatable
|
||||
|
||||
-- Lain markup util submodule
|
||||
-- lain.util.markup
|
||||
local markup = { fg = {}, bg = {} }
|
||||
|
||||
-- Convenience tags
|
||||
function markup.bold(text)
|
||||
return format('<b>%s</b>', text)
|
||||
end
|
||||
function markup.italic(text)
|
||||
return format('<i>%s</i>', text)
|
||||
end
|
||||
function markup.strike(text)
|
||||
return format('<s>%s</s>', text)
|
||||
end
|
||||
function markup.underline(text)
|
||||
return format('<u>%s</u>', text)
|
||||
end
|
||||
function markup.monospace(text)
|
||||
return format('<tt>%s</tt>', text)
|
||||
end
|
||||
function markup.big(text)
|
||||
return format('<big>%s</big>', text)
|
||||
end
|
||||
function markup.small(text)
|
||||
return format('<small>%s</small>', text)
|
||||
end
|
||||
|
||||
-- Set the font
|
||||
function markup.font(font, text)
|
||||
return format("<span font='%s'>%s</span>", font, text)
|
||||
end
|
||||
|
||||
-- Set the foreground
|
||||
function markup.fg.color(color, text)
|
||||
return format("<span foreground='%s'>%s</span>", color, text)
|
||||
end
|
||||
|
||||
-- Set the background
|
||||
function markup.bg.color(color, text)
|
||||
return format("<span background='%s'>%s</span>", color, text)
|
||||
end
|
||||
|
||||
-- Set foreground and background
|
||||
function markup.color(fg, bg, text)
|
||||
return format("<span foreground='%s' background='%s'>%s</span>", fg, bg, text)
|
||||
end
|
||||
|
||||
-- Set font and foreground
|
||||
function markup.fontfg(font, fg, text)
|
||||
return format("<span font='%s' foreground='%s'>%s</span>", font, fg, text)
|
||||
end
|
||||
|
||||
-- Set font and background
|
||||
function markup.fontbg(font, bg, text)
|
||||
return format("<span font='%s' background='%s'>%s</span>", font, bg, text)
|
||||
end
|
||||
|
||||
-- Set font, foreground and background
|
||||
function markup.fontcolor(font, fg, bg, text)
|
||||
return format("<span font='%s' foreground='%s' background='%s'>%s</span>", font, fg, bg, text)
|
||||
end
|
||||
|
||||
-- link markup.{fg,bg}(...) calls to markup.{fg,bg}.color(...)
|
||||
setmetatable(markup.fg, {
|
||||
__call = function(_, ...)
|
||||
return markup.fg.color(...)
|
||||
end,
|
||||
})
|
||||
setmetatable(markup.bg, {
|
||||
__call = function(_, ...)
|
||||
return markup.bg.color(...)
|
||||
end,
|
||||
})
|
||||
|
||||
-- link markup(...) calls to markup.fg.color(...)
|
||||
return setmetatable(markup, {
|
||||
__call = function(_, ...)
|
||||
return markup.fg.color(...)
|
||||
end,
|
||||
})
|
||||
151
lib/lain/util/menu_iterator.lua
Normal file
151
lib/lain/util/menu_iterator.lua
Normal file
@@ -0,0 +1,151 @@
|
||||
--[[
|
||||
|
||||
Licensed under GNU General Public License v2
|
||||
* (c) 2017, Simon Désaulniers <sim.desaulniers@gmail.com>
|
||||
* (c) 2017, Uli Schlachter
|
||||
* (c) 2017, Jeferson Siqueira <jefersonlsiq@gmail.com>
|
||||
|
||||
--]]
|
||||
|
||||
-- Menu iterator with Naughty notifications
|
||||
-- lain.util.menu_iterator
|
||||
|
||||
local naughty = require('naughty')
|
||||
local helpers = require('lain.helpers')
|
||||
local atable = require('awful.util').table
|
||||
local assert = assert
|
||||
local pairs = pairs
|
||||
local tconcat = table.concat
|
||||
local unpack = unpack or table.unpack -- lua 5.1 retro-compatibility
|
||||
|
||||
local state = { cid = nil }
|
||||
|
||||
local function naughty_destroy_callback(reason)
|
||||
local closed = naughty.notificationClosedReason
|
||||
if reason == closed.expired or reason == closed.dismissedByUser then
|
||||
local actions = state.index and state.menu[state.index - 1][2]
|
||||
if actions then
|
||||
for _, action in pairs(actions) do
|
||||
-- don't try to call nil callbacks
|
||||
if action then
|
||||
action()
|
||||
end
|
||||
end
|
||||
state.index = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Iterates over a menu.
|
||||
-- After the timeout, callbacks associated to the last visited choice are
|
||||
-- executed. Inputs:
|
||||
-- * menu: a list of {label, {callbacks}} pairs
|
||||
-- * timeout: time to wait before confirming the menu selection
|
||||
-- * icon: icon to display in the notification of the chosen label
|
||||
local function iterate(menu, timeout, icon)
|
||||
timeout = timeout or 4 -- default timeout for each menu entry
|
||||
icon = icon or nil -- icon to display on the menu
|
||||
|
||||
-- Build the list of choices
|
||||
if not state.index then
|
||||
state.menu = menu
|
||||
state.index = 1
|
||||
end
|
||||
|
||||
-- Select one and display the appropriate notification
|
||||
local label
|
||||
local next = state.menu[state.index]
|
||||
state.index = state.index + 1
|
||||
|
||||
if not next then
|
||||
label = 'Cancel'
|
||||
state.index = nil
|
||||
else
|
||||
label, _ = unpack(next)
|
||||
end
|
||||
|
||||
state.cid = naughty.notify({
|
||||
text = label,
|
||||
icon = icon,
|
||||
timeout = timeout,
|
||||
screen = mouse.screen,
|
||||
replaces_id = state.cid,
|
||||
destroy = naughty_destroy_callback,
|
||||
}).id
|
||||
end
|
||||
|
||||
-- Generates a menu compatible with the first argument of `iterate` function and
|
||||
-- suitable for the following cases:
|
||||
-- * all possible choices individually (partition of singletons);
|
||||
-- * all possible subsets of the set of choices (powerset).
|
||||
--
|
||||
-- Inputs:
|
||||
-- * args: an array containing the following members:
|
||||
-- * choices: Array of choices (string) on which the menu will be
|
||||
-- generated.
|
||||
-- * name: Displayed name of the menu (in the form "name: choices").
|
||||
-- * selected_cb: Callback to execute for each selected choice. Takes
|
||||
-- the choice as a string argument. Can be `nil` (no action
|
||||
-- to execute).
|
||||
-- * rejected_cb: Callback to execute for each rejected choice (possible
|
||||
-- choices which are not selected). Takes the choice as a
|
||||
-- string argument. Can be `nil` (no action to execute).
|
||||
-- * extra_choices: An array of extra { choice_str, callback_fun } pairs to be
|
||||
-- added to the menu. Each callback_fun can be `nil`.
|
||||
-- * combination: The combination of choices to generate. Possible values:
|
||||
-- "powerset" and "single" (default).
|
||||
-- Output:
|
||||
-- * m: menu to be iterated over.
|
||||
local function menu(args)
|
||||
local choices = assert(args.choices or args[1])
|
||||
local name = assert(args.name or args[2])
|
||||
local selected_cb = args.selected_cb
|
||||
local rejected_cb = args.rejected_cb
|
||||
local extra_choices = args.extra_choices or {}
|
||||
|
||||
local ch_combinations = args.combination == 'powerset' and helpers.powerset(choices)
|
||||
or helpers.trivial_partition_set(choices)
|
||||
|
||||
for _, c in pairs(extra_choices) do
|
||||
ch_combinations = atable.join(ch_combinations, { { c[1] } })
|
||||
end
|
||||
|
||||
local m = {} -- the menu
|
||||
|
||||
for _, c in pairs(ch_combinations) do
|
||||
if #c > 0 then
|
||||
local cbs = {}
|
||||
|
||||
-- selected choices
|
||||
for _, ch in pairs(c) do
|
||||
if atable.hasitem(choices, ch) then
|
||||
cbs[#cbs + 1] = selected_cb and function()
|
||||
selected_cb(ch)
|
||||
end or nil
|
||||
end
|
||||
end
|
||||
|
||||
-- rejected choices
|
||||
for _, ch in pairs(choices) do
|
||||
if not atable.hasitem(c, ch) and atable.hasitem(choices, ch) then
|
||||
cbs[#cbs + 1] = rejected_cb and function()
|
||||
rejected_cb(ch)
|
||||
end or nil
|
||||
end
|
||||
end
|
||||
|
||||
-- add user extra choices (like the choice "None" for example)
|
||||
for _, x in pairs(extra_choices) do
|
||||
if x[1] == c[1] then
|
||||
cbs[#cbs + 1] = x[2]
|
||||
end
|
||||
end
|
||||
|
||||
m[#m + 1] = { name .. ': ' .. tconcat(c, ' + '), cbs }
|
||||
end
|
||||
end
|
||||
|
||||
return m
|
||||
end
|
||||
|
||||
return { iterate = iterate, menu = menu }
|
||||
203
lib/lain/util/quake.lua
Normal file
203
lib/lain/util/quake.lua
Normal file
@@ -0,0 +1,203 @@
|
||||
--[[
|
||||
|
||||
Licensed under GNU General Public License v2
|
||||
* (c) 2016, Luca CPZ
|
||||
* (c) 2015, unknown
|
||||
|
||||
--]]
|
||||
|
||||
local awful = require('awful')
|
||||
local capi = { client = client }
|
||||
local math = math
|
||||
local string = string
|
||||
local pairs = pairs
|
||||
local screen = screen
|
||||
local setmetatable = setmetatable
|
||||
|
||||
-- Quake-like Dropdown application spawn
|
||||
local quake = {}
|
||||
|
||||
-- If you have a rule like "awful.client.setslave" for your terminals,
|
||||
-- ensure you use an exception for QuakeDD. Otherwise, you may
|
||||
-- run into problems with focus.
|
||||
|
||||
function quake:display()
|
||||
if self.followtag then
|
||||
self.screen = awful.screen.focused()
|
||||
end
|
||||
|
||||
-- First, we locate the client
|
||||
local client = nil
|
||||
local i = 0
|
||||
for c in
|
||||
awful.client.iterate(function(c)
|
||||
-- c.name may be changed!
|
||||
return c.instance == self.name
|
||||
end)
|
||||
do
|
||||
i = i + 1
|
||||
if i == 1 then
|
||||
client = c
|
||||
else
|
||||
-- Additional matching clients, let's remove the sticky bit
|
||||
-- which may persist between awesome restarts. We don't close
|
||||
-- them as they may be valuable. They will just turn into
|
||||
-- normal clients.
|
||||
c.sticky = false
|
||||
c.ontop = false
|
||||
c.above = false
|
||||
end
|
||||
end
|
||||
|
||||
if not client and not self.visible then
|
||||
return
|
||||
end
|
||||
|
||||
if not client then
|
||||
-- The client does not exist, we spawn it
|
||||
local cmd = string.format('%s %s %s', self.app, string.format(self.argname, self.name), self.extra)
|
||||
awful.spawn(cmd, { tag = self.screen.selected_tag })
|
||||
return
|
||||
end
|
||||
|
||||
-- Set geometry
|
||||
client.floating = true
|
||||
client.border_width = self.border
|
||||
client.size_hints_honor = false
|
||||
local maximized = client.maximized
|
||||
local fullscreen = client.fullscreen
|
||||
client:geometry(self.geometry[self.screen.index] or self:compute_size())
|
||||
|
||||
-- Set not sticky and on top
|
||||
client.sticky = false
|
||||
client.ontop = true
|
||||
client.above = true
|
||||
client.skip_taskbar = true
|
||||
|
||||
-- Additional user settings
|
||||
if self.settings then
|
||||
self.settings(client)
|
||||
end
|
||||
|
||||
-- Toggle display
|
||||
if self.visible then
|
||||
client.hidden = false
|
||||
client.maximized = self.maximized
|
||||
client.fullscreen = self.fullscreen
|
||||
client:raise()
|
||||
self.last_tag = self.screen.selected_tag
|
||||
client:tags({ self.screen.selected_tag })
|
||||
capi.client.focus = client
|
||||
else
|
||||
self.maximized = maximized
|
||||
self.fullscreen = fullscreen
|
||||
client.maximized = false
|
||||
client.fullscreen = false
|
||||
client.hidden = true
|
||||
local ctags = client:tags()
|
||||
for j, _ in pairs(ctags) do
|
||||
ctags[j] = nil
|
||||
end
|
||||
client:tags(ctags)
|
||||
end
|
||||
|
||||
return client
|
||||
end
|
||||
|
||||
function quake:compute_size()
|
||||
-- skip if we already have a geometry for this screen
|
||||
if not self.geometry[self.screen.index] then
|
||||
local geom
|
||||
if not self.overlap then
|
||||
geom = screen[self.screen.index].workarea
|
||||
else
|
||||
geom = screen[self.screen.index].geometry
|
||||
end
|
||||
local width, height = self.width, self.height
|
||||
if width <= 1 then
|
||||
width = math.floor(geom.width * width) - 2 * self.border
|
||||
end
|
||||
if height <= 1 then
|
||||
height = math.floor(geom.height * height)
|
||||
end
|
||||
local x, y
|
||||
if self.horiz == 'left' then
|
||||
x = geom.x
|
||||
elseif self.horiz == 'right' then
|
||||
x = geom.width + geom.x - width
|
||||
else
|
||||
x = geom.x + (geom.width - width) / 2
|
||||
end
|
||||
if self.vert == 'top' then
|
||||
y = geom.y
|
||||
elseif self.vert == 'bottom' then
|
||||
y = geom.height + geom.y - height
|
||||
else
|
||||
y = geom.y + (geom.height - height) / 2
|
||||
end
|
||||
self.geometry[self.screen.index] = { x = x, y = y, width = width, height = height }
|
||||
end
|
||||
return self.geometry[self.screen.index]
|
||||
end
|
||||
|
||||
function quake:toggle()
|
||||
if self.followtag then
|
||||
self.screen = awful.screen.focused()
|
||||
end
|
||||
local current_tag = self.screen.selected_tag
|
||||
if current_tag and self.last_tag ~= current_tag and self.visible then
|
||||
local c = self:display()
|
||||
if c then
|
||||
c:move_to_tag(current_tag)
|
||||
end
|
||||
else
|
||||
self.visible = not self.visible
|
||||
self:display()
|
||||
end
|
||||
end
|
||||
|
||||
function quake.new(conf)
|
||||
conf = conf or {}
|
||||
|
||||
conf.app = conf.app or 'xterm' -- application to spawn
|
||||
conf.name = conf.name or 'QuakeDD' -- window name
|
||||
conf.argname = conf.argname or '-name %s' -- how to specify window name
|
||||
conf.extra = conf.extra or '' -- extra arguments
|
||||
conf.border = conf.border or 1 -- client border width
|
||||
conf.visible = conf.visible or false -- initially not visible
|
||||
conf.followtag = conf.followtag or false -- spawn on currently focused screen
|
||||
conf.overlap = conf.overlap or false -- overlap wibox
|
||||
conf.screen = conf.screen or awful.screen.focused()
|
||||
conf.settings = conf.settings
|
||||
|
||||
-- If width or height <= 1 this is a proportion of the workspace
|
||||
conf.height = conf.height or 0.25 -- height
|
||||
conf.width = conf.width or 1 -- width
|
||||
conf.vert = conf.vert or 'top' -- top, bottom or center
|
||||
conf.horiz = conf.horiz or 'left' -- left, right or center
|
||||
conf.geometry = {} -- internal use
|
||||
|
||||
conf.maximized = false
|
||||
conf.fullscreen = false
|
||||
|
||||
local dropdown = setmetatable(conf, { __index = quake })
|
||||
|
||||
capi.client.connect_signal('manage', function(c)
|
||||
if c.instance == dropdown.name and c.screen == dropdown.screen then
|
||||
dropdown:display()
|
||||
end
|
||||
end)
|
||||
capi.client.connect_signal('unmanage', function(c)
|
||||
if c.instance == dropdown.name and c.screen == dropdown.screen then
|
||||
dropdown.visible = false
|
||||
end
|
||||
end)
|
||||
|
||||
return dropdown
|
||||
end
|
||||
|
||||
return setmetatable(quake, {
|
||||
__call = function(_, ...)
|
||||
return quake.new(...)
|
||||
end,
|
||||
})
|
||||
118
lib/lain/util/separators.lua
Normal file
118
lib/lain/util/separators.lua
Normal file
@@ -0,0 +1,118 @@
|
||||
--[[
|
||||
|
||||
Licensed under GNU General Public License v2
|
||||
* (c) 2015, Luca CPZ
|
||||
* (c) 2015, plotnikovanton
|
||||
|
||||
--]]
|
||||
|
||||
local wibox = require('wibox')
|
||||
local gears = require('gears')
|
||||
local beautiful = require('beautiful')
|
||||
|
||||
-- Lain Cairo separators util submodule
|
||||
-- lain.util.separators
|
||||
local separators = { height = beautiful.separators_height or 0, width = beautiful.separators_width or 9 }
|
||||
|
||||
-- [[ Arrow
|
||||
|
||||
-- Right
|
||||
function separators.arrow_right(col1, col2)
|
||||
local widget = wibox.widget.base.make_widget()
|
||||
widget.col1 = col1
|
||||
widget.col2 = col2
|
||||
|
||||
widget.fit = function(_, _, _)
|
||||
return separators.width, separators.height
|
||||
end
|
||||
|
||||
widget.update = function(_, _)
|
||||
widget.col1 = col1
|
||||
widget.col2 = col2
|
||||
widget:emit_signal('widget::redraw_needed')
|
||||
end
|
||||
|
||||
widget.draw = function(_, _, cr, width, height)
|
||||
if widget.col2 ~= 'alpha' then
|
||||
cr:set_source_rgba(gears.color.parse_color(widget.col2))
|
||||
cr:new_path()
|
||||
cr:move_to(0, 0)
|
||||
cr:line_to(width, height / 2)
|
||||
cr:line_to(width, 0)
|
||||
cr:close_path()
|
||||
cr:fill()
|
||||
|
||||
cr:new_path()
|
||||
cr:move_to(0, height)
|
||||
cr:line_to(width, height / 2)
|
||||
cr:line_to(width, height)
|
||||
cr:close_path()
|
||||
cr:fill()
|
||||
end
|
||||
|
||||
if widget.col1 ~= 'alpha' then
|
||||
cr:set_source_rgba(gears.color.parse_color(widget.col1))
|
||||
cr:new_path()
|
||||
cr:move_to(0, 0)
|
||||
cr:line_to(width, height / 2)
|
||||
cr:line_to(0, height)
|
||||
cr:close_path()
|
||||
cr:fill()
|
||||
end
|
||||
end
|
||||
|
||||
return widget
|
||||
end
|
||||
|
||||
-- Left
|
||||
function separators.arrow_left(col1, col2)
|
||||
local widget = wibox.widget.base.make_widget()
|
||||
widget.col1 = col1
|
||||
widget.col2 = col2
|
||||
|
||||
widget.fit = function(_, _, _)
|
||||
return separators.width, separators.height
|
||||
end
|
||||
|
||||
widget.update = function(c1, c2)
|
||||
widget.col1 = c1
|
||||
widget.col2 = c2
|
||||
widget:emit_signal('widget::redraw_needed')
|
||||
end
|
||||
|
||||
widget.draw = function(_, _, cr, width, height)
|
||||
if widget.col1 ~= 'alpha' then
|
||||
cr:set_source_rgba(gears.color.parse_color(widget.col1))
|
||||
cr:new_path()
|
||||
cr:move_to(width, 0)
|
||||
cr:line_to(0, height / 2)
|
||||
cr:line_to(0, 0)
|
||||
cr:close_path()
|
||||
cr:fill()
|
||||
|
||||
cr:new_path()
|
||||
cr:move_to(width, height)
|
||||
cr:line_to(0, height / 2)
|
||||
cr:line_to(0, height)
|
||||
cr:close_path()
|
||||
cr:fill()
|
||||
end
|
||||
|
||||
if widget.col2 ~= 'alpha' then
|
||||
cr:new_path()
|
||||
cr:move_to(width, 0)
|
||||
cr:line_to(0, height / 2)
|
||||
cr:line_to(width, height)
|
||||
cr:close_path()
|
||||
|
||||
cr:set_source_rgba(gears.color.parse_color(widget.col2))
|
||||
cr:fill()
|
||||
end
|
||||
end
|
||||
|
||||
return widget
|
||||
end
|
||||
|
||||
-- ]]
|
||||
|
||||
return separators
|
||||
Reference in New Issue
Block a user