Initial commit
This commit is contained in:
commit
8872d0f828
96
bencode.lua
Normal file
96
bencode.lua
Normal file
|
@ -0,0 +1,96 @@
|
|||
-- Based on bencode.lua from the jeejah project by Phil Hagelberg
|
||||
-- Distributed under the MIT license
|
||||
-- https://gitlab.com/technomancy/jeejah/
|
||||
|
||||
local encode, decode
|
||||
|
||||
local function decode_list(str, t, total_len)
|
||||
-- print("list", str, lume.serialize(t))
|
||||
if #str == 0 then error("Incomplete") end
|
||||
if(str:sub(1,1) == "e") then return t, total_len + 1 end
|
||||
local value, v_len = decode(str)
|
||||
table.insert(t, value)
|
||||
total_len = total_len + v_len
|
||||
return decode_list(str:sub(v_len + 1), t, total_len)
|
||||
end
|
||||
|
||||
local function decode_table(str, t, total_len)
|
||||
-- print("table", str, lume.serialize(t))
|
||||
if #str == 0 then error("Incomplete") end
|
||||
if(str:sub(1,1) == "e") then return t, total_len + 1 end
|
||||
local key, k_len = decode(str)
|
||||
local value, v_len = decode(str:sub(k_len+1))
|
||||
local end_pos = 1 + k_len + v_len
|
||||
t[key] = value
|
||||
total_len = total_len + k_len + v_len
|
||||
return decode_table(str:sub(end_pos), t, total_len)
|
||||
end
|
||||
|
||||
function decode(str)
|
||||
-- print("decoding", str)
|
||||
if #str == 0 then
|
||||
error("Incomplete")
|
||||
elseif(str:sub(1,1) == "l") then
|
||||
return decode_list(str:sub(2), {}, 1)
|
||||
elseif(str:sub(1,1) == "d") then
|
||||
return decode_table(str:sub(2), {}, 1)
|
||||
elseif(str:sub(1,1) == "i") then
|
||||
local iend = str:find("e")
|
||||
if iend == nil then error("Incomplete") end
|
||||
return(tonumber(str:sub(2, iend - 1))), iend
|
||||
elseif(str:match("[0-9]+:")) then
|
||||
local num_str = str:match("[0-9]+")
|
||||
local beginning_of_string = #num_str + 2
|
||||
local str_len = tonumber(num_str)
|
||||
local total_len = beginning_of_string + str_len - 1
|
||||
if #str < total_len then error("Incomplete") end
|
||||
return str:sub(beginning_of_string, total_len), total_len
|
||||
else
|
||||
error("Could not parse "..str)
|
||||
end
|
||||
end
|
||||
|
||||
local function encode_str(s) return #s .. ":" .. s end
|
||||
local function encode_int(n) return "i" .. tostring(n) .. "e" end
|
||||
|
||||
local function encode_table(t)
|
||||
-- sort keys by encoded value as per bencode spec
|
||||
-- https://www.bittorrent.org/beps/bep_0003.html#bencoding
|
||||
-- we assume that sorting the concatenated key-value pairs will result in the same ordering as just the keys, since keys are unique
|
||||
-- even if this is untrue in some corner cases, the most important thing for our purposes is that the same table always results in the same encoding
|
||||
local encoded_kvs = {}
|
||||
for k,v in pairs(t) do table.insert(encoded_kvs, encode(k) .. encode(v)) end
|
||||
table.sort(encoded_kvs)
|
||||
table.insert(encoded_kvs, 1, "d")
|
||||
table.insert(encoded_kvs, "e")
|
||||
return table.concat(encoded_kvs)
|
||||
end
|
||||
|
||||
local function encode_list(l)
|
||||
local s = "l"
|
||||
for _,x in ipairs(l) do s = s .. encode(x) end
|
||||
return s .. "e"
|
||||
end
|
||||
|
||||
local function count(tbl)
|
||||
local i = 0
|
||||
for _ in pairs(tbl) do i = i + 1 end
|
||||
return i
|
||||
end
|
||||
|
||||
function encode(x)
|
||||
local unpack = unpack or table.unpack
|
||||
if(type(x) == "table" and select("#", unpack(x)) == count(x)) then
|
||||
return encode_list(x)
|
||||
elseif(type(x) == "table") then
|
||||
return encode_table(x)
|
||||
elseif(type(x) == "number" and math.floor(x) == x) then
|
||||
return encode_int(x)
|
||||
elseif(type(x) == "string") then
|
||||
return encode_str(x)
|
||||
else
|
||||
error("Could not encode " .. type(x) .. ": " .. tostring(x))
|
||||
end
|
||||
end
|
||||
|
||||
return {decode=decode, encode=encode}
|
38
debug.fnl
Normal file
38
debug.fnl
Normal file
|
@ -0,0 +1,38 @@
|
|||
(local core (require :core))
|
||||
(local style (require :core.style))
|
||||
(local util (require :plugins.fennel-xl.util))
|
||||
(local repl (require :plugins.fennel-xl.repl))
|
||||
(local ReplView (require :plugins.fennel-xl.replview))
|
||||
(local replsession (require :plugins.fennel-xl.replsession))
|
||||
(require :plugins.fennel-xl.localrepl)
|
||||
(require :plugins.fennel-xl.nrepl-session)
|
||||
|
||||
(local module (util.hot-table ...))
|
||||
|
||||
(fn find-existing-inspector-window [name]
|
||||
(var result nil)
|
||||
(each [_ view (ipairs (core.root_view.root_node:get_children)) :until result]
|
||||
(when (= view.inspector-name name)
|
||||
(set result view)))
|
||||
result)
|
||||
|
||||
(fn create-inspector-window [name ?value]
|
||||
(let [node (core.root_view:get_active_node)
|
||||
conn (repl.new name (if ?value :local nil))
|
||||
view (ReplView conn)]
|
||||
(set view.inspector-name name)
|
||||
(set view.title name)
|
||||
(view:append {:draw (fn [_ _ x y] (when (and x y) (renderer.draw_text style.font name x y style.text) (+ (style.font:get_height) style.padding.y)))})
|
||||
(when ?value
|
||||
(view:append (repl.mk-result [?value])))
|
||||
(node:split :right view)
|
||||
view))
|
||||
|
||||
(lambda module.show [?value ?name]
|
||||
(let [name (replsession.session-id ?name)]
|
||||
(or (find-existing-inspector-window name) (create-inspector-window name ?value))))
|
||||
|
||||
(lambda module.submit [chunk ?name]
|
||||
(: (module.show ?name) :submit chunk))
|
||||
|
||||
module.hot
|
714
dkjson.lua
Normal file
714
dkjson.lua
Normal file
|
@ -0,0 +1,714 @@
|
|||
-- Module options:
|
||||
local always_try_using_lpeg = true
|
||||
local register_global_module_table = false
|
||||
local global_module_name = 'json'
|
||||
|
||||
--[==[
|
||||
|
||||
David Kolf's JSON module for Lua 5.1/5.2
|
||||
|
||||
Version 2.5
|
||||
|
||||
|
||||
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-2013 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, rawset =
|
||||
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
|
||||
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.5" }
|
||||
|
||||
if register_global_module_table then
|
||||
_G[global_module_name] = json
|
||||
end
|
||||
|
||||
local _ENV = nil -- blocking globals in Lua 5.2
|
||||
|
||||
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 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 len = strlen (str)
|
||||
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 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 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"\"" + Err "unterminated 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 npos
|
||||
local t, nt = {}, 0
|
||||
repeat
|
||||
obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
|
||||
if not npos then break end
|
||||
pos = npos
|
||||
nt = nt + 1
|
||||
t[nt] = obj
|
||||
until cont == 'last'
|
||||
return pos, setmetatable (t, state.arraymeta)
|
||||
end
|
||||
|
||||
local function parseobject (str, pos, nullval, state)
|
||||
local obj, key, cont
|
||||
local npos
|
||||
local t = {}
|
||||
repeat
|
||||
key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
|
||||
if not npos then break end
|
||||
pos = npos
|
||||
t[key] = obj
|
||||
until cont == 'last'
|
||||
return pos, setmetatable (t, state.objectmeta)
|
||||
end
|
||||
|
||||
local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray) * Space * (P"]" + Err "']' expected")
|
||||
local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject) * Space * (P"}" + Err "'}' expected")
|
||||
local Value = Space * (Array + Object + SimpleValue)
|
||||
local ExpectedValue = Value + Space * Err "value expected"
|
||||
ArrayContent = Value * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
|
||||
local Pair = g.Cg (Space * String * Space * (P":" + Err "colon expected") * ExpectedValue)
|
||||
ObjectContent = Pair * Space * (P"," * g.Cc'cont' + g.Cc'last') * g.Cp()
|
||||
local DecodeValue = ExpectedValue * g.Cp ()
|
||||
|
||||
function json.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
|
||||
|
||||
-- use this function only once:
|
||||
json.use_lpeg = function () return json end
|
||||
|
||||
json.using_lpeg = true
|
||||
|
||||
return json -- so you can get the module using json = require "dkjson".use_lpeg()
|
||||
end
|
||||
|
||||
if always_try_using_lpeg then
|
||||
pcall (json.use_lpeg)
|
||||
end
|
||||
|
||||
return json
|
||||
|
130
editor.fnl
Normal file
130
editor.fnl
Normal file
|
@ -0,0 +1,130 @@
|
|||
(local lume (require :plugins.fennel-xl.lume))
|
||||
(local util (require :plugins.fennel-xl.util))
|
||||
(local core (require :core))
|
||||
(local command (require :core.command))
|
||||
(local keymap (require :core.keymap))
|
||||
(local common (require :core.common))
|
||||
|
||||
(fn place-to-line [selection]
|
||||
(let [selection (lume.clone selection)]
|
||||
(when (selection:place?)
|
||||
(set selection.acol 1)
|
||||
(set selection.bcol math.huge))
|
||||
selection))
|
||||
|
||||
(fn get-selection []
|
||||
(let [ldoc core.active_view.doc
|
||||
(aline acol bline bcol) (ldoc:get_selection)]
|
||||
{: ldoc : aline : acol : bline : bcol
|
||||
:place? (fn [self] (and (= self.aline self.bline) (= self.acol self.bcol)))
|
||||
:rtl? (fn [self] (or (and (= self.aline self.bline) (< self.bcol self.acol)) (< self.bline self.aline)))
|
||||
:get-text (fn [self] (self.ldoc:get_text self.aline self.acol self.bline self.bcol))
|
||||
:replace-text (fn [self text]
|
||||
(self.ldoc:set_selection self.aline self.acol self.bline self.bcol)
|
||||
(self.ldoc:text_input text))}))
|
||||
|
||||
(fn to-place [selection ?beginning]
|
||||
(let [selection (lume.clone selection)]
|
||||
(if (or (and ?beginning (not (selection:rtl?))) (and (not ?beginning) (selection:rtl?)))
|
||||
(set (selection.bline selection.bcol) (values selection.aline selection.acol))
|
||||
(set (selection.aline selection.acol) (values selection.bline selection.bcol)))
|
||||
selection))
|
||||
|
||||
(fn selected-form [] (: (place-to-line (get-selection)) :get-text))
|
||||
|
||||
(fn find-closest [s pattern i-target]
|
||||
(var (start end) nil)
|
||||
(set (start end) (s:find pattern))
|
||||
(while (and start (< end (- i-target 1)))
|
||||
(set (start end) (s:find pattern (+ end 1))))
|
||||
(if (and start (<= start i-target)) (values start (+ end 1))
|
||||
(values i-target i-target)))
|
||||
|
||||
(local symbol-pattern "[a-zA-Z%!%@%#%$%%%^%&%*%<%>%?%/%~%-%_%=%+][a-zA-Z%!%@%#%$%%%^%&%*%<%>%?%/%~%-%_%=%+0-9%.%:]*")
|
||||
(fn place-to-symbol [selection]
|
||||
(let [selection (lume.clone selection)]
|
||||
(when (selection:place?)
|
||||
(let [line (: (place-to-line selection) :get-text)]
|
||||
(set (selection.acol selection.bcol) (find-closest line symbol-pattern selection.acol))))
|
||||
selection))
|
||||
|
||||
(fn selected-symbol [] (: (place-to-symbol (get-selection)) :get-text))
|
||||
|
||||
(fn inline-eval [eval] (: (to-place (get-selection)) :replace-text (eval (selected-form))))
|
||||
|
||||
(local {: show : submit} (util.require :plugins.fennel-xl.debug))
|
||||
(local replsession (require :plugins.fennel-xl.replsession))
|
||||
(local mm (util.require :plugins.fennel-xl.multimethod))
|
||||
(local nrepl (require :plugins.fennel-xl.nrepl))
|
||||
|
||||
(command.add :plugins.fennel-xl.replview {
|
||||
"repl:submit" #(core.active_view:submit)
|
||||
"repl:restart" #(replsession.restart core.active_view.conn.session)
|
||||
})
|
||||
|
||||
(command.add nil {
|
||||
"repl:create" #(show)
|
||||
"repl:switch-connection"
|
||||
#(core.command_view:enter "Connection Type"
|
||||
#(when $2.text (set replsession.default-repl $2.text))
|
||||
#(mm.keys replsession.submit))
|
||||
})
|
||||
|
||||
(command.add #(nrepl:connected?) {
|
||||
"nrepl:disconnect" #(nrepl:disconnect)
|
||||
})
|
||||
|
||||
(command.add #(not (nrepl:connected?)) {
|
||||
"nrepl:connect" #(nrepl:connect)
|
||||
})
|
||||
|
||||
(fn go-to-definition [symbol]
|
||||
(fn jump-to-find-result [result]
|
||||
(when (not (match result
|
||||
{:vals [loc]}
|
||||
(let [(filename line) (when loc (loc:match "(.*):([0-9]+)"))
|
||||
filename (or filename "")
|
||||
filename (if (filename:find "^%.%.%.") "" (or (filename:match "^%./(.*)") filename))
|
||||
line (tonumber (or line 0))
|
||||
ldoc (when (> (length filename) 0) (core.open_doc filename))]
|
||||
(when ldoc
|
||||
(core.root_view:open_doc ldoc)
|
||||
(ldoc:set_selection line 1 line 1)
|
||||
true))))
|
||||
(core.log (.. "Unable to find symbol " symbol))))
|
||||
(: (replsession.session) :submit (.. ",find " symbol) jump-to-find-result) true)
|
||||
|
||||
(fn replace-selected-symbol [text] (: (place-to-symbol (get-selection)) :replace-text text))
|
||||
|
||||
(fn autocomplete-results [text]
|
||||
(var symbols [])
|
||||
(: (replsession.session) :submit (.. ",complete " text) #(set symbols (or $1.vals [])) true)
|
||||
(icollect [_ symbol (ipairs symbols)]
|
||||
(let [item {:text symbol}]
|
||||
(: (replsession.session) :submit (.. ",doc " symbol) #(when $1.vals (set item.info (. $1.vals 1))))
|
||||
item)))
|
||||
|
||||
(fn autocomplete-symbol [callback ?starting-text]
|
||||
(fn fixup-result [text item] (callback (or (and item item.text) text)))
|
||||
(core.command_view:enter "Symbol" fixup-result autocomplete-results)
|
||||
(print "start-text" ?starting-text)
|
||||
(when ?starting-text
|
||||
(core.command_view:set_text ?starting-text)
|
||||
(core.command_view:update_suggestions)))
|
||||
|
||||
(command.add :core.docview {
|
||||
"fennel:eval" #(submit (selected-form))
|
||||
"fennel:go-to-definition-under-cursor" #(go-to-definition (selected-symbol))
|
||||
"fennel:go-to-definition" #(autocomplete-symbol #(go-to-definition $1) (selected-symbol))
|
||||
"fennel:autocomplete" #(autocomplete-symbol #(replace-selected-symbol $1) (selected-symbol))
|
||||
})
|
||||
|
||||
(keymap.add {
|
||||
:return "repl:submit"
|
||||
"alt+e" "fennel:eval"
|
||||
"alt+d" "fennel:go-to-definition-under-cursor"
|
||||
"ctrl+space" "fennel:autocomplete"
|
||||
})
|
||||
|
||||
{: inline-eval : symbol-pattern}
|
||||
|
5486
fennel.lua
Normal file
5486
fennel.lua
Normal file
File diff suppressed because it is too large
Load diff
390
imgui.fnl
Normal file
390
imgui.fnl
Normal file
|
@ -0,0 +1,390 @@
|
|||
(local core (require :core))
|
||||
(local config (require :core.config))
|
||||
(local command (require :core.command))
|
||||
(local keymap (require :core.keymap))
|
||||
(local style (require :core.style))
|
||||
(local lume (require :plugins.fennel-xl.lume))
|
||||
|
||||
(fn attach-imstate [view]
|
||||
(set view.imstate {})
|
||||
(fn view.on_mouse_pressed [self button x y clicks]
|
||||
(tset self.imstate button :pressed)
|
||||
(self.__index.on_mouse_pressed self button x y clicks))
|
||||
(fn view.on_mouse_released [self button x y]
|
||||
(tset self.imstate button :released)
|
||||
(self.__index.on_mouse_released self button x y))
|
||||
(fn view.on_key_pressed [self key]
|
||||
(when (= self.imstate.keys nil)
|
||||
(set self.imstate.keys []))
|
||||
(table.insert self.imstate.keys key))
|
||||
(fn view.on_text_input [self text]
|
||||
(set self.imstate.text (.. (or self.imstate.text "") text))
|
||||
(self.__index.on_text_input self text))
|
||||
(fn view.form [self ?overrides]
|
||||
(lume.merge {:x (+ self.position.x style.padding.x (- self.scroll.x))
|
||||
:y (+ self.position.y style.padding.y (- self.scroll.y))
|
||||
:w (- self.size.x (* style.padding.x 2))
|
||||
:view self}
|
||||
(or ?overrides {})))
|
||||
(fn view.end-scroll [self {: y : h}]
|
||||
(let [pin-to-bottom (>= self.scroll.to.y (- self.scrollheight self.size.y))]
|
||||
(set self.scrollheight (- (+ y (or h 0) style.padding.y) (+ self.position.y style.padding.y (- self.scroll.y))))
|
||||
(when pin-to-bottom (set self.scroll.to.y (- self.scrollheight self.size.y)))))
|
||||
(fn view.draw [self]
|
||||
(set self.cursor nil)
|
||||
(self.__index.draw self)
|
||||
(when self.imstate.postponed
|
||||
(each [_ action (ipairs self.imstate.postponed)]
|
||||
(action))
|
||||
(set self.imstate.postponed nil))
|
||||
(when (= self.cursor nil) (set self.cursor :arrow))
|
||||
(set self.imstate.keys nil)
|
||||
(set self.imstate.text nil)
|
||||
(when (= self.imstate.left :released)
|
||||
(set self.imstate.active nil))
|
||||
(each [_ button (pairs [:left :middle :right])]
|
||||
(tset self.imstate button
|
||||
(match (. self.imstate button)
|
||||
:pressed :down
|
||||
:down :down
|
||||
:released nil)))))
|
||||
|
||||
(fn register-keys [keys]
|
||||
(local commands {})
|
||||
(local keymaps {})
|
||||
(each [_ key (ipairs keys)]
|
||||
(local command-name (.. "imstate:" key))
|
||||
(tset commands command-name #(core.active_view:on_key_pressed key))
|
||||
(tset keymaps key command-name))
|
||||
(command.add #(not= (-?> core.active_view.imstate (. :focus)) nil) commands)
|
||||
(keymap.add keymaps))
|
||||
|
||||
(register-keys [:backspace :delete :left :right :shift+left :shift+right :home :end :shift+home :shift+end
|
||||
:ctrl+left :ctrl+right :ctrl+shift+left :ctrl+shift+right :ctrl+c :ctrl+v])
|
||||
|
||||
(fn cmd-predicate [p]
|
||||
(var p-fn p)
|
||||
(when (= (type p-fn) :string) (set p-fn (require p-fn)))
|
||||
(when (= (type p-fn) :table)
|
||||
(local cls p-fn)
|
||||
(set p-fn (fn [] (core.active_view:is cls))))
|
||||
(fn [] (when (= (-?> core.active_view.imstate (. :focus)) nil)
|
||||
(p-fn))))
|
||||
|
||||
(fn postpone [view f]
|
||||
(when (= view.imstate.postponed nil)
|
||||
(set view.imstate.postponed []))
|
||||
(table.insert view.imstate.postponed f))
|
||||
|
||||
(fn make-tag [tag]
|
||||
(match (type tag)
|
||||
:string tag
|
||||
:table (table.concat tag "::")
|
||||
_ (tostring tag)))
|
||||
|
||||
(fn mouse-inside [x y w h]
|
||||
(local (mx my) (values core.root_view.mouse.x core.root_view.mouse.y))
|
||||
(and (>= mx x) (<= mx (+ x w)) (>= my y) (<= my (+ y h))))
|
||||
|
||||
(fn consume-pressed [view button]
|
||||
(when (= (. view.imstate button) :pressed)
|
||||
(tset view.imstate button :down)
|
||||
true))
|
||||
|
||||
(fn activate [{: view : tag : x : y : w : h}]
|
||||
(when (and (mouse-inside x y w h) (consume-pressed view :left))
|
||||
(set view.imstate.active (make-tag tag))
|
||||
true))
|
||||
|
||||
(fn set-cursor [view cursor]
|
||||
(when (= view.cursor nil) (set view.cursor cursor)))
|
||||
|
||||
;; styling and layout
|
||||
(fn form-defaults [form k v ...]
|
||||
(when (= (. form k) nil)
|
||||
(let [v (if (= (type v) :function) (v form) v)]
|
||||
(tset form k v)))
|
||||
(if (>= (select :# ...) 2) (form-defaults form ...)
|
||||
(do (when form.tag (set form.tag (make-tag form.tag))) ; fix up tag
|
||||
form)))
|
||||
|
||||
(fn with-style [form ...]
|
||||
(form-defaults form :font style.font :color style.text :xpad style.padding.x :ypad style.padding.y ...))
|
||||
|
||||
(local form-preserved-keys (collect [_ key (ipairs [:view :x :y :font :color :xpad :ypad])] key true))
|
||||
(fn reform [form overrides]
|
||||
(if (and overrides overrides.into (not= overrides.into form))
|
||||
(reform (lume.extend (lume.clear overrides.into) form) overrides)
|
||||
(do (each [key (pairs form)]
|
||||
(when (= (. form-preserved-keys key) nil)
|
||||
(tset form key nil)))
|
||||
(lume.extend form (or overrides {})))))
|
||||
|
||||
(fn under [form overrides] (reform form (lume.merge (or overrides {}) {:y (+ form.y (or form.h 0) (or form.ypad 0))})))
|
||||
(fn right-of [form overrides] (reform form (lume.merge (or overrides {}) {:x (+ form.x (or form.w 0) (or form.xpad 0))})))
|
||||
|
||||
(fn group-wrapper [orig-form]
|
||||
(let [group {}
|
||||
update-dimension
|
||||
(fn [form coord-key size-key]
|
||||
(let [coord-group (. group coord-key) size-group (. group size-key)
|
||||
coord-form (. form coord-key) size-form (. form size-key)]
|
||||
(if (= size-form nil) ; tried to add an unsized value to the group, ignore
|
||||
nil
|
||||
|
||||
(= coord-group nil) ; container takes on the size of its first item
|
||||
(do (tset group coord-key coord-form)
|
||||
(tset group size-key size-form))
|
||||
|
||||
(> coord-group coord-form) ; we have an item that is outside the bounds to the left / up; reduce the starting point and extend the size
|
||||
(do (tset group coord-key coord-form)
|
||||
(tset group size-key (- (math.max (+ coord-form size-form) (+ coord-group size-group)) coord-form)))
|
||||
|
||||
; extend the size if the new item is outside the bounds to the right / down
|
||||
(tset group size-key (- (math.max (+ coord-form size-form) (+ coord-group size-group)) coord-group)))
|
||||
form))
|
||||
update-dimensions (fn [form] (update-dimension form :x :w) (update-dimension form :y :h))]
|
||||
|
||||
(fn [?viewfn-or-form ?form ...]
|
||||
(match [(type ?viewfn-or-form) ?viewfn-or-form]
|
||||
[:function viewfn] (let [result [(viewfn ?form ...)]]
|
||||
(update-dimensions ?form)
|
||||
(table.unpack result))
|
||||
[:table form] (update-dimensions form)
|
||||
[:nil] (lume.extend orig-form group)))))
|
||||
|
||||
(fn horiz-wrapper [{:x orig-x :w orig-w}]
|
||||
(fn [{: x : y : w : h : xpad : ypad &as form} overrides]
|
||||
(if (> (+ x (or w 0) xpad (or w 0)) (+ orig-x orig-w))
|
||||
(reform form (lume.merge (or overrides {}) {:x orig-x :y (+ y (or h 0) (or ypad 0))}))
|
||||
(right-of form overrides))))
|
||||
|
||||
;; widgets and widget helpers
|
||||
(fn active? [view tag] (= view.imstate.active (make-tag tag)))
|
||||
(fn button [{: view : tag : x : y : w : h &as form}]
|
||||
(when (mouse-inside x y w h) (set-cursor view :hand))
|
||||
(activate form)
|
||||
(and (active? view tag) (= view.imstate.left :released) (mouse-inside x y w h)))
|
||||
|
||||
(fn label [form text]
|
||||
(let [(_ newlines) (text:gsub "\n" "\n")
|
||||
text-height (fn [font] (* (font:get_height) (+ newlines 1)))
|
||||
{: x : y : w : h : halign : valign : font : color}
|
||||
(with-style form
|
||||
:w #($1.font:get_width text)
|
||||
:h #(text-height $1.font)
|
||||
:halign :left
|
||||
:valign :center)
|
||||
x (match halign :left x :center (+ x (/ (- w (font:get_width text)) 2)) :right (+ x w (- (font:get_width text))))
|
||||
y (match valign :top y :center (+ y (/ (- h (text-height font)) 2)) :bottom (+ y h (- (text-height font))))]
|
||||
(renderer.draw_text font text x y color)))
|
||||
|
||||
(fn textbutton [form label]
|
||||
(let [{: x : y : w : h : xpad : ypad : font : color : bg}
|
||||
(with-style form
|
||||
:bg style.selection
|
||||
:tag label
|
||||
:w #(+ ($1.font:get_width label) $1.xpad)
|
||||
:h #(+ ($1.font:get_height) $1.ypad))]
|
||||
(renderer.draw_rect x y w h bg)
|
||||
(renderer.draw_text font label (+ x (/ xpad 2)) (+ y (/ ypad 2)) color)
|
||||
(button form)))
|
||||
|
||||
(fn checkbox [form name isset]
|
||||
(let [{: x : y : w : h : font : color : bg : x-label}
|
||||
(with-style form
|
||||
:bg style.background
|
||||
:tag name
|
||||
:h (* 12 SCALE)
|
||||
:x-label #(+ $1.x $1.h $1.xpad)
|
||||
:w #(+ $1.x-label ($1.font:get_width name)))]
|
||||
(renderer.draw_rect x y h h color)
|
||||
(when (not isset) (renderer.draw_rect (+ x 2) (+ y 2) (- h 4) (- h 4)))
|
||||
(renderer.draw_text font name x-label y color)
|
||||
(button form))) ; whose idea was this?? should return (not isset) >:/
|
||||
|
||||
(fn focused? [view tag] (= (make-tag tag) (-?> view.imstate.focus (. :tag))))
|
||||
(fn focus [{: view : tag : x : y : w : h &as form} opts]
|
||||
(if (activate form)
|
||||
(set view.imstate.focus
|
||||
(doto (lume.clone (or opts {}))
|
||||
(tset :tag (make-tag tag))))
|
||||
|
||||
(and (= view.imstate.left :released) (focused? view tag) (not (mouse-inside x y w h)))
|
||||
(set view.imstate.focus nil))
|
||||
(focused? view tag))
|
||||
|
||||
(local blink_period 0.8)
|
||||
(fn x-from-i [s i xLeft font]
|
||||
(if (or (<= i 1) (= s "")) xLeft
|
||||
(x-from-i (s:sub 2) (- i 1) (+ xLeft (font:get_width (s:sub 1 1))) font)))
|
||||
(fn i-from-x [s x xLeft font ?i]
|
||||
(local i (or ?i 1))
|
||||
(local w (font:get_width (s:sub 1 1)))
|
||||
(local xMid (+ xLeft (/ w 2)))
|
||||
(if (or (<= x xMid) (= s "")) i
|
||||
(i-from-x (s:sub 2) x (+ xLeft w) font (+ i 1))))
|
||||
|
||||
(fn next-match [text i di pred]
|
||||
(local imax (+ (length text) 1))
|
||||
(local inext (+ i di))
|
||||
(if (<= inext 1) 1
|
||||
(> inext imax) imax
|
||||
(pred (text:sub inext inext)) (if (< di 0) i inext)
|
||||
(next-match text inext di pred)))
|
||||
(fn is-nonword-char [char] (config.non_word_chars:find char nil true))
|
||||
(fn next-word [text i di]
|
||||
(let [iwordboundary (next-match text i di #(is-nonword-char $1))]
|
||||
(next-match text iwordboundary di #(not (is-nonword-char $1)))))
|
||||
|
||||
(fn textnav [key i text]
|
||||
(local imax (+ (length text) 1))
|
||||
(match key
|
||||
:left (math.max 1 (- i 1))
|
||||
:right (math.min imax (+ i 1))
|
||||
:ctrl+left (next-word text i -1)
|
||||
:ctrl+right (next-word text i 1)
|
||||
:home 1
|
||||
:end imax))
|
||||
|
||||
(fn selection-span [view]
|
||||
(let [f view.imstate.focus
|
||||
iStart (math.min f.i f.iAnchor)
|
||||
iLim (math.max f.i f.iAnchor)]
|
||||
(values iStart iLim)))
|
||||
(fn selection-text [view text]
|
||||
(local (iStart iLim) (selection-span view))
|
||||
(text:sub iStart (- iLim 1)))
|
||||
|
||||
(fn replace-selection [view s replacement ?iStart ?iLim]
|
||||
(local (iStart iLim) (if ?iLim (values ?iStart ?iLim) (selection-span view)))
|
||||
(local text
|
||||
(.. (s:sub 1 (- iStart 1))
|
||||
replacement
|
||||
(s:sub iLim)))
|
||||
(local iNew (+ iStart (length replacement)))
|
||||
(set view.imstate.focus.i iNew)
|
||||
(set view.imstate.focus.iAnchor iNew)
|
||||
text)
|
||||
|
||||
(fn textbox [form text]
|
||||
(local {: font : color : w : h : x : y : xpad : ypad : color : bg : view : tag}
|
||||
(with-style form :h #(+ ($1.font:get_height) $1.ypad)
|
||||
:bg style.background))
|
||||
(var textNew (or text ""))
|
||||
(local (hText xText yText) (values (font:get_height) (+ x (/ xpad 2)) (+ y (/ ypad 2))))
|
||||
(local initial-press (= view.imstate.left :pressed))
|
||||
|
||||
; handle key events
|
||||
(when (focus form {:i 1 :iAnchor 1 :blink (system.get_time)})
|
||||
(local f view.imstate.focus)
|
||||
(when (> f.i (+ (length textNew) 1)) (set f.i (+ (length textNew) 1)))
|
||||
(when (> f.iAnchor (+ (length textNew) 1)) (set f.iAnchor (+ (length textNew) 1)))
|
||||
(when view.imstate.text
|
||||
(set textNew (replace-selection view textNew view.imstate.text)))
|
||||
(each [_ key (ipairs (or view.imstate.keys []))]
|
||||
(set view.imstate.focus.blink (system.get_time))
|
||||
(if (= key :ctrl+c) (system.set_clipboard (selection-text view textNew))
|
||||
(= key :ctrl+v) (set textNew (replace-selection view textNew (system.get_clipboard)))
|
||||
(key:find "shift%+") (set f.i (or (textnav (key:gsub "shift%+" "") f.i textNew) f.i))
|
||||
(let [iNav (textnav key f.i textNew)]
|
||||
(when iNav
|
||||
(set f.i iNav)
|
||||
(set f.iAnchor iNav))
|
||||
(when (or (= key :delete) (= key :backspace))
|
||||
(local (iStartDel iLimDel)
|
||||
(if (not= f.i f.iAnchor) (selection-span view)
|
||||
(= key :delete) (values f.i (+ f.i 1))
|
||||
(= key :backspace) (values (math.max 1 (- f.i 1)) f.i)))
|
||||
(set textNew (replace-selection view textNew "" iStartDel iLimDel)))))))
|
||||
|
||||
; handle mouse events
|
||||
(when (mouse-inside x y w h) (set-cursor view :ibeam))
|
||||
(when (and (focused? view tag) (active? view tag) (mouse-inside x y w h))
|
||||
(local mouse-i (i-from-x textNew core.root_view.mouse.x x style.font))
|
||||
(when initial-press
|
||||
(set view.imstate.focus.iAnchor mouse-i))
|
||||
(set view.imstate.focus.i mouse-i))
|
||||
|
||||
; draw box
|
||||
(renderer.draw_rect x y w h color)
|
||||
(renderer.draw_rect (+ x 1) (+ y 1) (- w 2) (- h 2) bg)
|
||||
(if (focused? view tag)
|
||||
; draw text with selection + caret
|
||||
(let [(iStart iLim) (selection-span view)
|
||||
xSelect (renderer.draw_text font (textNew:sub 1 (- iStart 1)) xText yText color)
|
||||
sSelect (textNew:sub iStart (- iLim 1))
|
||||
wSelect (font:get_width sSelect)
|
||||
xTail (+ xSelect wSelect)]
|
||||
(when (> wSelect 0)
|
||||
(renderer.draw_rect xSelect yText wSelect hText style.selection)
|
||||
(renderer.draw_text font sSelect xSelect yText color))
|
||||
(renderer.draw_text font (textNew:sub iLim) xTail yText color)
|
||||
(when (or (active? view tag)
|
||||
(< (% (- (system.get_time) view.imstate.focus.blink) (* blink_period 2)) blink_period))
|
||||
(renderer.draw_rect (x-from-i textNew view.imstate.focus.i xText font) yText style.caret_width hText style.caret)))
|
||||
; just draw the text
|
||||
(renderer.draw_text font textNew xText yText color))
|
||||
textNew)
|
||||
|
||||
(fn textfield [form label text]
|
||||
(let [{: x : y : w : wlabel : wtext : font : color}
|
||||
(with-style form :wlabel #(+ ($1.font:get_width label) $1.xpad)
|
||||
:wtext (* 150 SCALE)
|
||||
:w #(+ $1.wlabel $1.wtext)
|
||||
:tag label)
|
||||
form-textbox (lume.merge form {:w wtext :x (+ x wlabel)})
|
||||
_ (renderer.draw_text font label x y color)
|
||||
text (textbox form-textbox text)]
|
||||
(set form.h form-textbox.h)
|
||||
text))
|
||||
|
||||
(fn option-text [option]
|
||||
(match (type option)
|
||||
:string option
|
||||
:table (or option.label (tostring option))
|
||||
_ (tostring option)))
|
||||
|
||||
(fn dropdown [form selection options]
|
||||
(let [{: x : y : w :h row-h : font : color : bg : xpad : ypad : view : tag}
|
||||
(with-style form :w (* 150 SCALE)
|
||||
:h #(+ ($1.font:get_height) $1.ypad)
|
||||
:bg style.selection)]
|
||||
(var new-selection nil)
|
||||
|
||||
(renderer.draw_rect x y w row-h bg)
|
||||
(renderer.draw_text style.font (option-text selection) (+ x xpad) (+ y (/ ypad 2)) color)
|
||||
(renderer.draw_text style.icon_font "-" (+ x w (- xpad)) (+ y (/ ypad 2)) color)
|
||||
|
||||
(when (focused? view tag)
|
||||
(var row-y (+ y row-h))
|
||||
(each [i option (ipairs options)]
|
||||
(when (button (lume.merge form {:tag [(make-tag tag) i] :y row-y}))
|
||||
(set new-selection option))
|
||||
(set row-y (+ row-y row-h)))
|
||||
(postpone view (fn []
|
||||
(var row-y (+ y row-h))
|
||||
(each [i option (ipairs options)]
|
||||
(renderer.draw_rect x row-y w row-h bg)
|
||||
(renderer.draw_text font (option-text option) (+ x xpad) (+ row-y (/ ypad 2)) color)
|
||||
(set row-y (+ row-y row-h))))))
|
||||
(focus form)
|
||||
(or new-selection selection)))
|
||||
|
||||
(fn labelled-dropdown [form label selection options]
|
||||
(let [{: x : y : wlabel : wdropdown : font : color}
|
||||
(with-style form :wlabel #(+ ($1.font:get_width label) $1.xpad)
|
||||
:wdropdown (* 150 SCALE)
|
||||
:w #(+ $1.wlabel $1.wdropdown)
|
||||
:tag label)
|
||||
form-dropdown (lume.merge form {:x (+ x wlabel) :w wdropdown})
|
||||
_ (renderer.draw_text font label x y color)
|
||||
selection (dropdown form-dropdown selection options)]
|
||||
(set form.h form-dropdown.h)
|
||||
selection))
|
||||
|
||||
{: attach-imstate : cmd-predicate : postpone : mouse-inside : activate : active?
|
||||
: button : checkbox : textbox : textfield : textbutton : dropdown : labelled-dropdown : label
|
||||
: reform : under : right-of : horiz-wrapper : group-wrapper
|
||||
: with-style : form-defaults}
|
||||
|
8
init.lua
Normal file
8
init.lua
Normal file
|
@ -0,0 +1,8 @@
|
|||
-- mod-version:2 -- lite-xl 2.0
|
||||
local fennel = require "plugins.fennel-xl.fennel"
|
||||
fennel.path = package.path:gsub("%.lua", ".fnl")
|
||||
debug.traceback = fennel.traceback
|
||||
table.insert(package.loaders or package.searchers, fennel.searcher)
|
||||
|
||||
require "plugins.fennel-xl.editor"
|
||||
|
55
inspector.fnl
Normal file
55
inspector.fnl
Normal file
|
@ -0,0 +1,55 @@
|
|||
(local fennel (require :plugins.fennel-xl.fennel))
|
||||
(local util (require :plugins.fennel-xl.util))
|
||||
(local style (require :core.style))
|
||||
(local {: defmulti : defmethod} (util.require :plugins.fennel-xl.multimethod))
|
||||
(local {: textbutton : label : under : right-of : reform : group-wrapper } (util.require :plugins.fennel-xl.imgui))
|
||||
|
||||
(local inspector (util.hot-table ...))
|
||||
|
||||
(fn inspector.best-inspector [v]
|
||||
(var best-inspector nil)
|
||||
(var best-priority -1)
|
||||
(each [inspector {: priority : predicate} (pairs inspector.inspectors)]
|
||||
(when (and (> priority best-priority) (predicate v))
|
||||
(set best-inspector inspector)
|
||||
(set best-priority priority)))
|
||||
best-inspector)
|
||||
|
||||
(set inspector.inspect
|
||||
(defmulti (fn [form state value]
|
||||
(when (= state.inspector nil)
|
||||
(set state.inspector (inspector.best-inspector value)))
|
||||
state.inspector) :inspect ...))
|
||||
|
||||
(fn inspector.register [name priority predicate inspect-func]
|
||||
(when (= inspector.inspectors nil)
|
||||
(set inspector.inspectors {}))
|
||||
(tset inspector.inspectors name {: predicate : priority :inspector inspect-func})
|
||||
(defmethod inspector.inspect name inspect-func))
|
||||
|
||||
(inspector.register :default 0 #true (fn [form state value]
|
||||
(label (reform form {:font style.code_font}) (fennel.view value))))
|
||||
|
||||
(inspector.register :table 10
|
||||
#(and (= (type $1) :table) (not= (next $1) nil))
|
||||
(fn [form state tbl]
|
||||
(let [get-kstate (fn [tbl k state]
|
||||
(when (= nil state.keys) (set state.keys {}))
|
||||
(when (= nil (?. state.keys k))
|
||||
(util.nested-tset state [:keys k] {:collapsed (= (type (. tbl k)) :table) :children {}}))
|
||||
(. state.keys k))
|
||||
g (group-wrapper form)]
|
||||
(each [k v (pairs tbl)]
|
||||
(let [kstate (get-kstate tbl k state)]
|
||||
; todo: state assumes an .inspector key
|
||||
; todo: inspector swapping
|
||||
; todo: edit in place?
|
||||
(when (g textbutton (under form {:font style.code_font}) (fennel.view k))
|
||||
(set kstate.collapsed (not kstate.collapsed)))
|
||||
(if kstate.collapsed
|
||||
(g label (right-of form {:color style.syntax.comment :into {}}) "...")
|
||||
(g #(inspector.inspect $...) (right-of form {:into {}}) kstate.children v))
|
||||
(g))))))
|
||||
|
||||
inspector.hot
|
||||
|
32
localrepl.fnl
Normal file
32
localrepl.fnl
Normal file
|
@ -0,0 +1,32 @@
|
|||
(local fennel (require :plugins.fennel-xl.fennel))
|
||||
(local lume (require :plugins.fennel-xl.lume))
|
||||
(local util (require :plugins.fennel-xl.util))
|
||||
(local {: defmethod} (require :plugins.fennel-xl.multimethod))
|
||||
(local {: submit : restart} (require :plugins.fennel-xl.replsession))
|
||||
(local core (require :core))
|
||||
|
||||
(local localrepl (util.hot-table ...))
|
||||
|
||||
(fn localrepl.session-run [session]
|
||||
(fennel.repl {:readChunk coroutine.yield
|
||||
; todo: log errors?
|
||||
:onValues #(pcall session.callback {:vals $1})
|
||||
:onError #(pcall session.callback {:errType $1 :err $2 :luaSource $3 :traceback (fennel.traceback)})
|
||||
:pp #$1
|
||||
:env (lume.clone _G)}))
|
||||
|
||||
(defmethod restart :local (fn [session]
|
||||
(set session.coro (coroutine.create localrepl.session-run))
|
||||
(coroutine.resume session.coro session)
|
||||
session))
|
||||
|
||||
(defmethod submit :local (fn [session chunk callback ?suppress-crash]
|
||||
(assert (= session.callback nil))
|
||||
(set session.callback callback)
|
||||
(match (pcall coroutine.resume session.coro (.. chunk "\n"))
|
||||
(false err) (do (when (not ?suppress-crash) (core.log (.. "REPL crashed: " err)))
|
||||
(restart session)))
|
||||
(assert (= session.callback callback))
|
||||
(set session.callback nil)))
|
||||
|
||||
localrepl.hot
|
780
lume.lua
Normal file
780
lume.lua
Normal file
|
@ -0,0 +1,780 @@
|
|||
--
|
||||
-- lume
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- 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.
|
||||
--
|
||||
|
||||
local lume = { _version = "2.3.0" }
|
||||
|
||||
local pairs, ipairs = pairs, ipairs
|
||||
local type, assert, unpack = type, assert, rawget(_G, "unpack") or table.unpack
|
||||
local tostring, tonumber = tostring, tonumber
|
||||
local math_floor = math.floor
|
||||
local math_ceil = math.ceil
|
||||
local math_atan2 = math.atan2 or math.atan
|
||||
local math_sqrt = math.sqrt
|
||||
local math_abs = math.abs
|
||||
|
||||
local noop = function()
|
||||
end
|
||||
|
||||
local identity = function(x)
|
||||
return x
|
||||
end
|
||||
|
||||
local patternescape = function(str)
|
||||
return str:gsub("[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1")
|
||||
end
|
||||
|
||||
local absindex = function(len, i)
|
||||
return i < 0 and (len + i + 1) or i
|
||||
end
|
||||
|
||||
local iscallable = function(x)
|
||||
if type(x) == "function" then return true end
|
||||
local mt = getmetatable(x)
|
||||
return mt and mt.__call ~= nil
|
||||
end
|
||||
|
||||
local getiter = function(x)
|
||||
if lume.isarray(x) then
|
||||
return ipairs
|
||||
elseif type(x) == "table" then
|
||||
return pairs
|
||||
end
|
||||
error("expected table", 3)
|
||||
end
|
||||
|
||||
local iteratee = function(x)
|
||||
if x == nil then return identity end
|
||||
if iscallable(x) then return x end
|
||||
if type(x) == "table" then
|
||||
return function(z)
|
||||
for k, v in pairs(x) do
|
||||
if z[k] ~= v then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
return function(z) return z[x] end
|
||||
end
|
||||
|
||||
|
||||
|
||||
function lume.clamp(x, min, max)
|
||||
return x < min and min or (x > max and max or x)
|
||||
end
|
||||
|
||||
|
||||
function lume.round(x, increment)
|
||||
if increment then return lume.round(x / increment) * increment end
|
||||
return x >= 0 and math_floor(x + .5) or math_ceil(x - .5)
|
||||
end
|
||||
|
||||
|
||||
function lume.sign(x)
|
||||
return x < 0 and -1 or 1
|
||||
end
|
||||
|
||||
|
||||
function lume.lerp(a, b, amount)
|
||||
return a + (b - a) * lume.clamp(amount, 0, 1)
|
||||
end
|
||||
|
||||
|
||||
function lume.smooth(a, b, amount)
|
||||
local t = lume.clamp(amount, 0, 1)
|
||||
local m = t * t * (3 - 2 * t)
|
||||
return a + (b - a) * m
|
||||
end
|
||||
|
||||
|
||||
function lume.pingpong(x)
|
||||
return 1 - math_abs(1 - x % 2)
|
||||
end
|
||||
|
||||
|
||||
function lume.distance(x1, y1, x2, y2, squared)
|
||||
local dx = x1 - x2
|
||||
local dy = y1 - y2
|
||||
local s = dx * dx + dy * dy
|
||||
return squared and s or math_sqrt(s)
|
||||
end
|
||||
|
||||
|
||||
function lume.angle(x1, y1, x2, y2)
|
||||
return math_atan2(y2 - y1, x2 - x1)
|
||||
end
|
||||
|
||||
|
||||
function lume.vector(angle, magnitude)
|
||||
return math.cos(angle) * magnitude, math.sin(angle) * magnitude
|
||||
end
|
||||
|
||||
|
||||
function lume.random(a, b)
|
||||
if not a then a, b = 0, 1 end
|
||||
if not b then b = 0 end
|
||||
return a + math.random() * (b - a)
|
||||
end
|
||||
|
||||
|
||||
function lume.randomchoice(t)
|
||||
return t[math.random(#t)]
|
||||
end
|
||||
|
||||
|
||||
function lume.weightedchoice(t)
|
||||
local sum = 0
|
||||
for _, v in pairs(t) do
|
||||
assert(v >= 0, "weight value less than zero")
|
||||
sum = sum + v
|
||||
end
|
||||
assert(sum ~= 0, "all weights are zero")
|
||||
local rnd = lume.random(sum)
|
||||
for k, v in pairs(t) do
|
||||
if rnd < v then return k end
|
||||
rnd = rnd - v
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.isarray(x)
|
||||
return type(x) == "table" and x[1] ~= nil
|
||||
end
|
||||
|
||||
|
||||
function lume.push(t, ...)
|
||||
local n = select("#", ...)
|
||||
for i = 1, n do
|
||||
t[#t + 1] = select(i, ...)
|
||||
end
|
||||
return ...
|
||||
end
|
||||
|
||||
|
||||
function lume.remove(t, x)
|
||||
local iter = getiter(t)
|
||||
for i, v in iter(t) do
|
||||
if v == x then
|
||||
if lume.isarray(t) then
|
||||
table.remove(t, i)
|
||||
break
|
||||
else
|
||||
t[i] = nil
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
|
||||
function lume.clear(t)
|
||||
local iter = getiter(t)
|
||||
for k in iter(t) do
|
||||
t[k] = nil
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.extend(t, ...)
|
||||
for i = 1, select("#", ...) do
|
||||
local x = select(i, ...)
|
||||
if x then
|
||||
for k, v in pairs(x) do
|
||||
t[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.shuffle(t)
|
||||
local rtn = {}
|
||||
for i = 1, #t do
|
||||
local r = math.random(i)
|
||||
if r ~= i then
|
||||
rtn[i] = rtn[r]
|
||||
end
|
||||
rtn[r] = t[i]
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.sort(t, comp)
|
||||
local rtn = lume.clone(t)
|
||||
if comp then
|
||||
if type(comp) == "string" then
|
||||
table.sort(rtn, function(a, b) return a[comp] < b[comp] end)
|
||||
else
|
||||
table.sort(rtn, comp)
|
||||
end
|
||||
else
|
||||
table.sort(rtn)
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.array(...)
|
||||
local t = {}
|
||||
for x in ... do t[#t + 1] = x end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.each(t, fn, ...)
|
||||
local iter = getiter(t)
|
||||
if type(fn) == "string" then
|
||||
for _, v in iter(t) do v[fn](v, ...) end
|
||||
else
|
||||
for _, v in iter(t) do fn(v, ...) end
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
|
||||
function lume.map(t, fn)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
local rtn = {}
|
||||
for k, v in iter(t) do rtn[k] = fn(v) end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.all(t, fn)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
for _, v in iter(t) do
|
||||
if not fn(v) then return false end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
function lume.any(t, fn)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
for _, v in iter(t) do
|
||||
if fn(v) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
function lume.reduce(t, fn, first)
|
||||
local started = first ~= nil
|
||||
local acc = first
|
||||
local iter = getiter(t)
|
||||
for _, v in iter(t) do
|
||||
if started then
|
||||
acc = fn(acc, v)
|
||||
else
|
||||
acc = v
|
||||
started = true
|
||||
end
|
||||
end
|
||||
assert(started, "reduce of an empty table with no first value")
|
||||
return acc
|
||||
end
|
||||
|
||||
|
||||
function lume.unique(t)
|
||||
local rtn = {}
|
||||
for k in pairs(lume.invert(t)) do
|
||||
rtn[#rtn + 1] = k
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.filter(t, fn, retainkeys)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
local rtn = {}
|
||||
if retainkeys then
|
||||
for k, v in iter(t) do
|
||||
if fn(v) then rtn[k] = v end
|
||||
end
|
||||
else
|
||||
for _, v in iter(t) do
|
||||
if fn(v) then rtn[#rtn + 1] = v end
|
||||
end
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.reject(t, fn, retainkeys)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
local rtn = {}
|
||||
if retainkeys then
|
||||
for k, v in iter(t) do
|
||||
if not fn(v) then rtn[k] = v end
|
||||
end
|
||||
else
|
||||
for _, v in iter(t) do
|
||||
if not fn(v) then rtn[#rtn + 1] = v end
|
||||
end
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.merge(...)
|
||||
local rtn = {}
|
||||
for i = 1, select("#", ...) do
|
||||
local t = select(i, ...)
|
||||
local iter = getiter(t)
|
||||
for k, v in iter(t) do
|
||||
rtn[k] = v
|
||||
end
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.concat(...)
|
||||
local rtn = {}
|
||||
for i = 1, select("#", ...) do
|
||||
local t = select(i, ...)
|
||||
if t ~= nil then
|
||||
local iter = getiter(t)
|
||||
for _, v in iter(t) do
|
||||
rtn[#rtn + 1] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.find(t, value)
|
||||
local iter = getiter(t)
|
||||
for k, v in iter(t) do
|
||||
if v == value then return k end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
function lume.match(t, fn)
|
||||
fn = iteratee(fn)
|
||||
local iter = getiter(t)
|
||||
for k, v in iter(t) do
|
||||
if fn(v) then return v, k end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
function lume.count(t, fn)
|
||||
local count = 0
|
||||
local iter = getiter(t)
|
||||
if fn then
|
||||
fn = iteratee(fn)
|
||||
for _, v in iter(t) do
|
||||
if fn(v) then count = count + 1 end
|
||||
end
|
||||
else
|
||||
if lume.isarray(t) then
|
||||
return #t
|
||||
end
|
||||
for _ in iter(t) do count = count + 1 end
|
||||
end
|
||||
return count
|
||||
end
|
||||
|
||||
|
||||
function lume.slice(t, i, j)
|
||||
i = i and absindex(#t, i) or 1
|
||||
j = j and absindex(#t, j) or #t
|
||||
local rtn = {}
|
||||
for x = i < 1 and 1 or i, j > #t and #t or j do
|
||||
rtn[#rtn + 1] = t[x]
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.first(t, n)
|
||||
if not n then return t[1] end
|
||||
return lume.slice(t, 1, n)
|
||||
end
|
||||
|
||||
|
||||
function lume.last(t, n)
|
||||
if not n then return t[#t] end
|
||||
return lume.slice(t, -n, -1)
|
||||
end
|
||||
|
||||
|
||||
function lume.invert(t)
|
||||
local rtn = {}
|
||||
for k, v in pairs(t) do rtn[v] = k end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.pick(t, ...)
|
||||
local rtn = {}
|
||||
for i = 1, select("#", ...) do
|
||||
local k = select(i, ...)
|
||||
rtn[k] = t[k]
|
||||
end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.keys(t)
|
||||
local rtn = {}
|
||||
local iter = getiter(t)
|
||||
for k in iter(t) do rtn[#rtn + 1] = k end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.clone(t)
|
||||
local rtn = {}
|
||||
for k, v in pairs(t) do rtn[k] = v end
|
||||
return rtn
|
||||
end
|
||||
|
||||
|
||||
function lume.fn(fn, ...)
|
||||
assert(iscallable(fn), "expected a function as the first argument")
|
||||
local args = { ... }
|
||||
return function(...)
|
||||
local a = lume.concat(args, { ... })
|
||||
return fn(unpack(a))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.once(fn, ...)
|
||||
local f = lume.fn(fn, ...)
|
||||
local done = false
|
||||
return function(...)
|
||||
if done then return end
|
||||
done = true
|
||||
return f(...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local memoize_fnkey = {}
|
||||
local memoize_nil = {}
|
||||
|
||||
function lume.memoize(fn)
|
||||
local cache = {}
|
||||
return function(...)
|
||||
local c = cache
|
||||
for i = 1, select("#", ...) do
|
||||
local a = select(i, ...) or memoize_nil
|
||||
c[a] = c[a] or {}
|
||||
c = c[a]
|
||||
end
|
||||
c[memoize_fnkey] = c[memoize_fnkey] or {fn(...)}
|
||||
return unpack(c[memoize_fnkey])
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.combine(...)
|
||||
local n = select('#', ...)
|
||||
if n == 0 then return noop end
|
||||
if n == 1 then
|
||||
local fn = select(1, ...)
|
||||
if not fn then return noop end
|
||||
assert(iscallable(fn), "expected a function or nil")
|
||||
return fn
|
||||
end
|
||||
local funcs = {}
|
||||
for i = 1, n do
|
||||
local fn = select(i, ...)
|
||||
if fn ~= nil then
|
||||
assert(iscallable(fn), "expected a function or nil")
|
||||
funcs[#funcs + 1] = fn
|
||||
end
|
||||
end
|
||||
return function(...)
|
||||
for _, f in ipairs(funcs) do f(...) end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.call(fn, ...)
|
||||
if fn then
|
||||
return fn(...)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.time(fn, ...)
|
||||
local start = os.clock()
|
||||
local rtn = {fn(...)}
|
||||
return (os.clock() - start), unpack(rtn)
|
||||
end
|
||||
|
||||
|
||||
local lambda_cache = {}
|
||||
|
||||
function lume.lambda(str)
|
||||
if not lambda_cache[str] then
|
||||
local args, body = str:match([[^([%w,_ ]-)%->(.-)$]])
|
||||
assert(args and body, "bad string lambda")
|
||||
local s = "return function(" .. args .. ")\nreturn " .. body .. "\nend"
|
||||
lambda_cache[str] = lume.dostring(s)
|
||||
end
|
||||
return lambda_cache[str]
|
||||
end
|
||||
|
||||
|
||||
local serialize
|
||||
|
||||
local serialize_map = {
|
||||
[ "boolean" ] = tostring,
|
||||
[ "nil" ] = tostring,
|
||||
[ "string" ] = function(v) return string.format("%q", v) end,
|
||||
[ "number" ] = function(v)
|
||||
if v ~= v then return "0/0" -- nan
|
||||
elseif v == 1 / 0 then return "1/0" -- inf
|
||||
elseif v == -1 / 0 then return "-1/0" end -- -inf
|
||||
return tostring(v)
|
||||
end,
|
||||
[ "table" ] = function(t, stk)
|
||||
stk = stk or {}
|
||||
if stk[t] then error("circular reference") end
|
||||
local rtn = {}
|
||||
stk[t] = true
|
||||
for k, v in pairs(t) do
|
||||
rtn[#rtn + 1] = "[" .. serialize(k, stk) .. "]=" .. serialize(v, stk)
|
||||
end
|
||||
stk[t] = nil
|
||||
return "{" .. table.concat(rtn, ",") .. "}"
|
||||
end
|
||||
}
|
||||
|
||||
setmetatable(serialize_map, {
|
||||
__index = function(_, k) error("unsupported serialize type: " .. k) end
|
||||
})
|
||||
|
||||
serialize = function(x, stk)
|
||||
return serialize_map[type(x)](x, stk)
|
||||
end
|
||||
|
||||
function lume.serialize(x)
|
||||
return serialize(x)
|
||||
end
|
||||
|
||||
|
||||
function lume.deserialize(str)
|
||||
return lume.dostring("return " .. str)
|
||||
end
|
||||
|
||||
|
||||
function lume.split(str, sep)
|
||||
if not sep then
|
||||
return lume.array(str:gmatch("([%S]+)"))
|
||||
else
|
||||
assert(sep ~= "", "empty separator")
|
||||
local psep = patternescape(sep)
|
||||
return lume.array((str..sep):gmatch("(.-)("..psep..")"))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function lume.trim(str, chars)
|
||||
if not chars then return str:match("^[%s]*(.-)[%s]*$") end
|
||||
chars = patternescape(chars)
|
||||
return str:match("^[" .. chars .. "]*(.-)[" .. chars .. "]*$")
|
||||
end
|
||||
|
||||
|
||||
function lume.wordwrap(str, limit)
|
||||
limit = limit or 72
|
||||
local check
|
||||
if type(limit) == "number" then
|
||||
check = function(s) return #s >= limit end
|
||||
else
|
||||
check = limit
|
||||
end
|
||||
local rtn = {}
|
||||
local line = ""
|
||||
for word, spaces in str:gmatch("(%S+)(%s*)") do
|
||||
local s = line .. word
|
||||
if check(s) then
|
||||
table.insert(rtn, line .. "\n")
|
||||
line = word
|
||||
else
|
||||
line = s
|
||||
end
|
||||
for c in spaces:gmatch(".") do
|
||||
if c == "\n" then
|
||||
table.insert(rtn, line .. "\n")
|
||||
line = ""
|
||||
else
|
||||
line = line .. c
|
||||
end
|
||||
end
|
||||
end
|
||||
table.insert(rtn, line)
|
||||
return table.concat(rtn)
|
||||
end
|
||||
|
||||
|
||||
function lume.format(str, vars)
|
||||
if not vars then return str end
|
||||
local f = function(x)
|
||||
return tostring(vars[x] or vars[tonumber(x)] or "{" .. x .. "}")
|
||||
end
|
||||
return (str:gsub("{(.-)}", f))
|
||||
end
|
||||
|
||||
|
||||
function lume.trace(...)
|
||||
local info = debug.getinfo(2, "Sl")
|
||||
local t = { info.short_src .. ":" .. info.currentline .. ":" }
|
||||
for i = 1, select("#", ...) do
|
||||
local x = select(i, ...)
|
||||
if type(x) == "number" then
|
||||
x = string.format("%g", lume.round(x, .01))
|
||||
end
|
||||
t[#t + 1] = tostring(x)
|
||||
end
|
||||
print(table.concat(t, " "))
|
||||
end
|
||||
|
||||
|
||||
function lume.dostring(str)
|
||||
return assert((loadstring or load)(str))()
|
||||
end
|
||||
|
||||
|
||||
function lume.uuid()
|
||||
local fn = function(x)
|
||||
local r = math.random(16) - 1
|
||||
r = (x == "x") and (r + 1) or (r % 4) + 9
|
||||
return ("0123456789abcdef"):sub(r, r)
|
||||
end
|
||||
return (("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"):gsub("[xy]", fn))
|
||||
end
|
||||
|
||||
|
||||
function lume.hotswap(modname)
|
||||
local oldglobal = lume.clone(_G)
|
||||
local updated = {}
|
||||
local function update(old, new)
|
||||
if updated[old] then return end
|
||||
updated[old] = true
|
||||
local oldmt, newmt = getmetatable(old), getmetatable(new)
|
||||
if oldmt and newmt then update(oldmt, newmt) end
|
||||
for k, v in pairs(new) do
|
||||
if type(v) == "table" then update(old[k], v) else old[k] = v end
|
||||
end
|
||||
end
|
||||
local err = nil
|
||||
local function onerror(e)
|
||||
for k in pairs(_G) do _G[k] = oldglobal[k] end
|
||||
err = lume.trim(e)
|
||||
end
|
||||
local ok, oldmod = pcall(require, modname)
|
||||
oldmod = ok and oldmod or nil
|
||||
xpcall(function()
|
||||
package.loaded[modname] = nil
|
||||
local newmod = require(modname)
|
||||
if type(oldmod) == "table" then update(oldmod, newmod) end
|
||||
for k, v in pairs(oldglobal) do
|
||||
if v ~= _G[k] and type(v) == "table" then
|
||||
update(v, _G[k])
|
||||
_G[k] = v
|
||||
end
|
||||
end
|
||||
end, onerror)
|
||||
package.loaded[modname] = oldmod
|
||||
if err then return nil, err end
|
||||
return oldmod
|
||||
end
|
||||
|
||||
|
||||
local ripairs_iter = function(t, i)
|
||||
i = i - 1
|
||||
local v = t[i]
|
||||
if v ~= nil then
|
||||
return i, v
|
||||
end
|
||||
end
|
||||
|
||||
function lume.ripairs(t)
|
||||
return ripairs_iter, t, (#t + 1)
|
||||
end
|
||||
|
||||
|
||||
function lume.color(str, mul)
|
||||
mul = mul or 1
|
||||
local r, g, b, a
|
||||
r, g, b = str:match("#(%x%x)(%x%x)(%x%x)")
|
||||
if r then
|
||||
r = tonumber(r, 16) / 0xff
|
||||
g = tonumber(g, 16) / 0xff
|
||||
b = tonumber(b, 16) / 0xff
|
||||
a = 1
|
||||
elseif str:match("rgba?%s*%([%d%s%.,]+%)") then
|
||||
local f = str:gmatch("[%d.]+")
|
||||
r = (f() or 0) / 0xff
|
||||
g = (f() or 0) / 0xff
|
||||
b = (f() or 0) / 0xff
|
||||
a = f() or 1
|
||||
else
|
||||
error(("bad color string '%s'"):format(str))
|
||||
end
|
||||
return r * mul, g * mul, b * mul, a * mul
|
||||
end
|
||||
|
||||
|
||||
local chain_mt = {}
|
||||
chain_mt.__index = lume.map(lume.filter(lume, iscallable, true),
|
||||
function(fn)
|
||||
return function(self, ...)
|
||||
self._value = fn(self._value, ...)
|
||||
return self
|
||||
end
|
||||
end)
|
||||
chain_mt.__index.result = function(x) return x._value end
|
||||
|
||||
function lume.chain(value)
|
||||
return setmetatable({ _value = value }, chain_mt)
|
||||
end
|
||||
|
||||
setmetatable(lume, {
|
||||
__call = function(_, ...)
|
||||
return lume.chain(...)
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
return lume
|
21
multimethod.fnl
Normal file
21
multimethod.fnl
Normal file
|
@ -0,0 +1,21 @@
|
|||
(local util (require :plugins.fennel-xl.util))
|
||||
|
||||
(local mm {})
|
||||
|
||||
(fn mm.__call [{: module : name} ...]
|
||||
(let [dispatcher (. mm.dispatchers module name)
|
||||
key (dispatcher ...)
|
||||
method (or (. mm.methods module name key) (. mm.methods module name :default))]
|
||||
(method ...)))
|
||||
|
||||
(fn mm.defmulti [dispatcher name module]
|
||||
(util.nested-tset mm [:dispatchers module name] dispatcher)
|
||||
(setmetatable {: module : name} mm))
|
||||
|
||||
(fn mm.defmethod [{: module : name} key method]
|
||||
(util.nested-tset mm [:methods module name key] method))
|
||||
|
||||
(fn mm.keys [{: module : name}]
|
||||
(icollect [key _ (pairs (or (?. mm :methods module name) []))] key))
|
||||
|
||||
mm
|
93
nrepl-session.fnl
Normal file
93
nrepl-session.fnl
Normal file
|
@ -0,0 +1,93 @@
|
|||
(local Object (require :core.object))
|
||||
(local nrepl (require :plugins.fennel-xl.nrepl))
|
||||
(local lume (require :plugins.fennel-xl.lume))
|
||||
(local {: defmethod} (require :plugins.fennel-xl.multimethod))
|
||||
(local {: restart : submit} (require :plugins.fennel-xl.replsession))
|
||||
|
||||
(local Session (Object:extend))
|
||||
|
||||
(fn Session.new [self ?handlers]
|
||||
(set self.queue [])
|
||||
(set self.in-progress false)
|
||||
(set self.handlers ?handlers))
|
||||
|
||||
(fn Session.init-session [self]
|
||||
(when (nrepl:connected?)
|
||||
(self:do #(nrepl:new-session
|
||||
#(do (set self.session $2)
|
||||
(self:done-msg))
|
||||
(self:make-handlers)))))
|
||||
|
||||
(fn Session.shutdown-session [self]
|
||||
(set self.queue [])
|
||||
(set self.in-progress false)
|
||||
(set self.session nil))
|
||||
|
||||
(fn Session.cleanup-handlers [self]
|
||||
{:status/done #(self:done-msg)
|
||||
:status/interrupted #(self:done-msg)})
|
||||
|
||||
(fn Session.make-handlers [self]
|
||||
(lume.merge
|
||||
(or self.handlers {})
|
||||
(nrepl:chain-handlers [:status/done :status/interrupted]
|
||||
(or self.handlers {})
|
||||
(self:cleanup-handlers))))
|
||||
|
||||
(fn Session.coro-handlers [self coro ?handlers]
|
||||
(lume.merge
|
||||
(or ?handlers {})
|
||||
(nrepl:chain-handlers [:status/done :status/interrupted]
|
||||
(self:cleanup-handlers)
|
||||
{:status/done #(coroutine.resume coro)
|
||||
:status/interrupted #(coroutine.resume coro)})))
|
||||
|
||||
(fn Session.do [self f]
|
||||
(if self.in-progress (table.insert self.queue f)
|
||||
(do (set self.in-progress true)
|
||||
(f))))
|
||||
|
||||
(fn Session.done-msg [self]
|
||||
(if (> (length self.queue) 0) ((table.remove self.queue 1))
|
||||
(set self.in-progress false)))
|
||||
|
||||
(fn Session.send [self message ?handlers]
|
||||
(self:do #(nrepl:send message ?handlers self.session)))
|
||||
|
||||
(fn Session.send-oob [self message ?handlers]
|
||||
(local handlers
|
||||
(lume.merge
|
||||
(nrepl:chain-handlers [:status/done :status/interrupted]
|
||||
(or self.handlers {}))
|
||||
(or ?handlers {})))
|
||||
(nrepl:send message handlers self.session))
|
||||
|
||||
(fn Session.eval [self code ?handlers]
|
||||
(self:send {:op :eval : code} ?handlers))
|
||||
|
||||
(fn Session.input-handler [self input]
|
||||
{:status/need-input #(self:send-oob {:op :stdin :stdin input})})
|
||||
|
||||
(fn Session.eval-input [self code input ?handlers]
|
||||
(self:send {:op :eval : code}
|
||||
(lume.merge (or ?handlers {}) (self:input-handler input))))
|
||||
|
||||
(local fennel (require :plugins.fennel-xl.fennel))
|
||||
(fn parse-vals [s]
|
||||
(let [parser (fennel.parser s)]
|
||||
(icollect [ok ast (fennel.parser s)] (if ok ast s))))
|
||||
|
||||
(defmethod restart :nrepl (fn [session]
|
||||
(when (not (nrepl:connected?)) (error "Must connect nrepl"))
|
||||
(when session.conn (session.conn:shutdown-session))
|
||||
(set session.conn (Session))
|
||||
(session.conn:init-session)
|
||||
session))
|
||||
|
||||
(defmethod submit :nrepl (fn [session chunk callback ?suppress-crash]
|
||||
(session.conn:eval chunk
|
||||
{:out #(callback {:vals [$2]})
|
||||
:value #(callback {:vals (parse-vals $2)})
|
||||
:ex #(callback {:err $2})})))
|
||||
|
||||
Session
|
111
nrepl.fnl
Normal file
111
nrepl.fnl
Normal file
|
@ -0,0 +1,111 @@
|
|||
(local core (require :core))
|
||||
(local socket (require :socket))
|
||||
; (local socket {:connect #nil})
|
||||
(local bencode (require :plugins.fennel-xl.bencode))
|
||||
(local lume (require :plugins.fennel-xl.lume))
|
||||
|
||||
(fn contains? [tbl item]
|
||||
(or (= tbl item) (lume.find tbl item)))
|
||||
|
||||
(local nrepl
|
||||
{:active-requests {}
|
||||
:session-handlers {}
|
||||
:default-handlers
|
||||
{:out #(core.log $2)
|
||||
:value #(core.log $2)
|
||||
:ex #(core.err $2)
|
||||
:status/interrupted #($1:done $3.id)
|
||||
:status/done #($1:done $3.id)}
|
||||
:merge-handlers
|
||||
(fn [self message]
|
||||
(lume.merge self.default-handlers
|
||||
(or (. self.session-handlers message.session) {})
|
||||
(or (. self.active-requests message.id) {})))
|
||||
:chain-handlers
|
||||
(fn [self keys ...]
|
||||
(local new-handlers {})
|
||||
(each [_ key (ipairs keys)]
|
||||
(each [_ handlers (ipairs [self.default-handlers ...])]
|
||||
(local next-handler (. handlers key))
|
||||
(local prev-handler (. new-handlers key))
|
||||
(if (and next-handler prev-handler)
|
||||
(tset new-handlers key
|
||||
#(do (prev-handler $1 $2 $3) (next-handler $1 $2 $3)))
|
||||
next-handler
|
||||
(tset new-handlers key next-handler))))
|
||||
new-handlers)
|
||||
:counter 1
|
||||
:input ""
|
||||
:parse-input
|
||||
(fn [self]
|
||||
(match (pcall #(bencode.decode self.input))
|
||||
(true val len)
|
||||
(do (set self.input (self.input:sub (+ len 1)))
|
||||
val)
|
||||
(false :Incomplete) nil
|
||||
(false _)
|
||||
(do (set self.input "")
|
||||
nil)))
|
||||
:receive
|
||||
(fn [self]
|
||||
(when self.connection
|
||||
(local (data err part) (self.connection:receive "*a"))
|
||||
(local response (or data part))
|
||||
(when (> (response:len) 0)
|
||||
(set self.input (.. self.input response)))
|
||||
(match (self:parse-input) nil nil
|
||||
input (self:handle input))
|
||||
(when (= err :closed)
|
||||
(self:disconnect))))
|
||||
:send
|
||||
(fn [self msg ?handlers ?session]
|
||||
(when self.connection
|
||||
(when (not msg.id)
|
||||
(set msg.id self.counter)
|
||||
(set self.counter (+ self.counter 1)))
|
||||
(when (not msg.session)
|
||||
(set msg.session (or ?session self.default-session)))
|
||||
(when ?handlers
|
||||
(tset self.active-requests msg.id ?handlers))
|
||||
(self.connection:send (bencode.encode msg))))
|
||||
:done
|
||||
(fn [self msg-id]
|
||||
(tset self.active-requests msg-id nil))
|
||||
:handle
|
||||
(fn [self response]
|
||||
(local handlers (self:merge-handlers response))
|
||||
(each [prop handler (pairs handlers)]
|
||||
(local idiv (prop:find :/))
|
||||
(local key (if idiv (prop:sub 1 (- idiv 1)) prop))
|
||||
(when (and (. response key)
|
||||
(or (= idiv nil)
|
||||
(contains? (. response key) (prop:sub (+ idiv 1)))))
|
||||
(handler self (. response key) response))))
|
||||
:disconnect
|
||||
(fn [self]
|
||||
(when self.connection
|
||||
(self.connection:close)
|
||||
(set self.connection nil)
|
||||
(set self.default-session nil)
|
||||
(set self.active-requests {})
|
||||
(set self.session-handlers {})))
|
||||
:connect
|
||||
(fn [self ?port ?host]
|
||||
(when (not self.connection)
|
||||
(local (port host) (values (or ?port 7888) (or ?host :localhost)))
|
||||
(set self.connection (assert (socket.connect host port)))
|
||||
(self.connection:settimeout 0)
|
||||
(core.add_thread #(while true (self:receive) (coroutine.yield)) self)
|
||||
(self:send {:op :clone}
|
||||
{:new-session #(set self.default-session $2)})))
|
||||
:connected?
|
||||
(fn [self] (not= self.default-session nil))
|
||||
:new-session
|
||||
(fn [self callback ?handler]
|
||||
(self:send {:op :clone}
|
||||
{:new-session
|
||||
(fn [self session message]
|
||||
(tset self.session-handlers session ?handler)
|
||||
(callback self session message))}))})
|
||||
|
||||
nrepl
|
40
repl.fnl
Normal file
40
repl.fnl
Normal file
|
@ -0,0 +1,40 @@
|
|||
(local util (require :plugins.fennel-xl.util))
|
||||
(local fennel (require :plugins.fennel-xl.fennel))
|
||||
(local style (require :core.style))
|
||||
(local lume (require :plugins.fennel-xl.lume))
|
||||
(local {: textbutton : under : group-wrapper} (util.require :plugins.fennel-xl.imgui))
|
||||
(local {: inspect} (util.require :plugins.fennel-xl.inspector))
|
||||
(local replsession (require :plugins.fennel-xl.replsession))
|
||||
|
||||
(local repl (util.hot-table ...))
|
||||
|
||||
(fn repl.inspector [{: w &as form} {: vals : states}]
|
||||
(let [g (group-wrapper form)]
|
||||
(each [i v (ipairs vals)]
|
||||
(g #(inspect $...) (under (g) {: w}) (. states i) v))
|
||||
(g)))
|
||||
|
||||
(fn repl.notify [listeners line]
|
||||
(each [_ listener (ipairs listeners)]
|
||||
(listener:append line)))
|
||||
|
||||
(fn repl.mk-result [vals]
|
||||
{:draw repl.inspector : vals :states (icollect [_ (ipairs vals)] {})})
|
||||
|
||||
(fn repl.listen [{: listeners} listener]
|
||||
(table.insert listeners listener))
|
||||
|
||||
(fn repl.unlisten [{: listeners} listener]
|
||||
(lume.remove listeners listener))
|
||||
|
||||
(fn repl.submit [{: session : listeners} chunk]
|
||||
(session:submit chunk (fn [{: vals : err : traceback}] (repl.notify listeners (repl.mk-result (or vals [err traceback]))))))
|
||||
|
||||
(fn repl.new [?id ?repl ?opts]
|
||||
{:listeners []
|
||||
:listen #(repl.listen $...)
|
||||
:unlisten #(repl.unlisten $...)
|
||||
:submit #(repl.submit $...)
|
||||
:session (replsession.session ?id ?repl ?opts)})
|
||||
|
||||
repl.hot
|
32
replsession.fnl
Normal file
32
replsession.fnl
Normal file
|
@ -0,0 +1,32 @@
|
|||
(local util (require :plugins.fennel-xl.util))
|
||||
(local lume (require :plugins.fennel-xl.lume))
|
||||
(local {: defmulti} (require :plugins.fennel-xl.multimethod))
|
||||
|
||||
(local replsession (util.hot-table ...))
|
||||
|
||||
(set replsession.sessions {})
|
||||
(set replsession.default-repl :local)
|
||||
|
||||
(set replsession.submit
|
||||
(defmulti (fn [session chunk callback ?suppress-crash] session.repl) :submit ...))
|
||||
(set replsession.restart (defmulti (fn [session] session.repl) :restart ...))
|
||||
|
||||
(fn replsession.new-session [?repl ?opts]
|
||||
(replsession.restart (lume.merge {:repl (or ?repl replsession.default-repl)
|
||||
:submit replsession.submit}
|
||||
(or ?opts {}))))
|
||||
|
||||
(fn replsession.activate [id] (set replsession.active-session-id id))
|
||||
(fn replsession.session-id [?id ?repl]
|
||||
(let [repl (or ?repl replsession.default-repl)]
|
||||
(if (= ?id nil) (or replsession.active-session-id (.. repl ":REPL"))
|
||||
(.. repl ":" ?id))))
|
||||
|
||||
(fn replsession.session [?id ?repl ?opts]
|
||||
(let [id (replsession.session-id ?id ?repl)
|
||||
session (or (. replsession.sessions id) (replsession.new-session ?repl ?opts))]
|
||||
(tset replsession.sessions id session)
|
||||
session))
|
||||
|
||||
replsession.hot
|
||||
|
60
replview.fnl
Normal file
60
replview.fnl
Normal file
|
@ -0,0 +1,60 @@
|
|||
(local util (require :plugins.fennel-xl.util))
|
||||
(local {: attach-imstate : textbox : textbutton : label : under : reform : group-wrapper : mouse-inside} (util.require :plugins.fennel-xl.imgui))
|
||||
(local View (require :core.view))
|
||||
(local style (require :core.style))
|
||||
|
||||
(local ReplView (View:extend))
|
||||
|
||||
(fn ReplView.new [self conn]
|
||||
(ReplView.super.new self)
|
||||
(attach-imstate self)
|
||||
(set self.conn conn)
|
||||
(set self.log [])
|
||||
(set self.cmd "")
|
||||
(set self.scrollheight math.huge)
|
||||
(set self.scrollable true)
|
||||
(set self.title "REPL")
|
||||
(self.conn:listen self))
|
||||
|
||||
(fn ReplView.try_close [self do_close]
|
||||
(self.conn:unlisten self)
|
||||
(ReplView.super.try_close self do_close))
|
||||
|
||||
(fn ReplView.get_scrollable_size [self] self.scrollheight)
|
||||
|
||||
(fn ReplView.append [self line]
|
||||
(table.insert self.log line))
|
||||
|
||||
(fn ReplView.draw-cmd [{: x : y : w : view &as form} {: cmd} iline]
|
||||
(label form cmd)
|
||||
(when (mouse-inside x y w form.h)
|
||||
(when (textbutton (reform form {:x (+ x w -35) :into {}}) :X)
|
||||
(table.remove view.log iline)
|
||||
(table.remove view.log iline))
|
||||
(when (textbutton (reform form {:x (+ x w -60) :into {}}) :!)
|
||||
(view:submit cmd))))
|
||||
|
||||
(fn ReplView.submit [self ?cmd]
|
||||
(local cmd (or ?cmd self.cmd))
|
||||
(when (= ?cmd nil)
|
||||
(set self.cmd ""))
|
||||
(self:append {:draw self.draw-cmd : cmd})
|
||||
(self.conn:submit cmd))
|
||||
|
||||
(fn ReplView.draw [self]
|
||||
(self:draw_background style.background)
|
||||
(self:draw_scrollbar)
|
||||
(let [{: w &as form} (self:form)
|
||||
g (group-wrapper form)]
|
||||
; todo: cache sizes and avoid drawing if offscreen?
|
||||
; note: then offscreen items can't be focussed without further effort
|
||||
; todo: draw line numbers
|
||||
(each [i line (ipairs self.log)]
|
||||
(g line.draw (under (g) {: w}) line i))
|
||||
(set self.cmd (g textbox (under (g) {: w :tag :command}) self.cmd))
|
||||
(self:end-scroll (g))))
|
||||
|
||||
(fn ReplView.get_name [self] self.title)
|
||||
|
||||
ReplView
|
||||
|
116
util.fnl
Normal file
116
util.fnl
Normal file
|
@ -0,0 +1,116 @@
|
|||
(local lume (require :plugins.fennel-xl.lume))
|
||||
(local json (require :plugins.fennel-xl.dkjson))
|
||||
(local core (require :core))
|
||||
|
||||
(fn string.fromhex [str]
|
||||
(str:gsub ".." (fn [cc] (string.char (tonumber cc 16)))))
|
||||
(fn string.tohex [str]
|
||||
(str:gsub "." (fn [c] (string.format "%02X" (string.byte c)))))
|
||||
|
||||
(fn splice [bytes offset str]
|
||||
(.. (bytes:sub 1 offset)
|
||||
str
|
||||
(bytes:sub (+ (length str) offset 1))))
|
||||
|
||||
(fn reload [modname]
|
||||
(tset package.loaded modname nil)
|
||||
(require modname))
|
||||
|
||||
; lume.hotswap really assumes your module is a table
|
||||
(fn hotswap [modname]
|
||||
(if (= (type (. package.loaded modname)) :table)
|
||||
(lume.hotswap modname)
|
||||
(reload modname)))
|
||||
|
||||
(fn mk-swappable-fn [table k]
|
||||
(fn [...] ((. table k) ...)))
|
||||
|
||||
(fn swappable [table]
|
||||
(local s {})
|
||||
(each [k v (pairs table)]
|
||||
(if (= (type v) :function)
|
||||
(tset s k (mk-swappable-fn table k))
|
||||
(tset s k v)))
|
||||
s)
|
||||
|
||||
(fn swappable-require [modname]
|
||||
(swappable (require modname)))
|
||||
|
||||
(fn hot-table [modname]
|
||||
(local tbl {})
|
||||
(fn find-table []
|
||||
(let [loaded-pkg (. package.loaded modname)]
|
||||
(if (= (type loaded-pkg) :table) loaded-pkg tbl)))
|
||||
(setmetatable {:hot tbl} {
|
||||
:__index (fn [_ key] (. (find-table) key))
|
||||
:__newindex (fn [_ key val] (tset (find-table) key val))
|
||||
}))
|
||||
|
||||
(fn readjson [filename]
|
||||
(local f (io.open filename :r))
|
||||
(local text (f:read "*a"))
|
||||
(f:close)
|
||||
(json.decode text))
|
||||
|
||||
(fn writejson [filename value]
|
||||
(local f (io.open filename :w))
|
||||
(f:write (json.encode value))
|
||||
(f:close))
|
||||
|
||||
(fn waitfor [pred]
|
||||
(local coro (coroutine.running))
|
||||
(core.add_thread
|
||||
(fn []
|
||||
(while (not (pred))
|
||||
(coroutine.yield))
|
||||
(coroutine.resume coro))
|
||||
coro)
|
||||
(coroutine.yield))
|
||||
|
||||
(fn in-coro [f ...] (-> (coroutine.create f) (coroutine.resume ...)))
|
||||
|
||||
(fn multival-next [multival i]
|
||||
(when (< i multival.n)
|
||||
(values (+ i 1) (. multival (+ i 1)))))
|
||||
|
||||
(fn multival-ipairs [multival]
|
||||
(values multival-next multival 0))
|
||||
|
||||
(fn multival [...]
|
||||
(local multival {:n (select :# ...) :ipairs multival-ipairs})
|
||||
(for [i 1 multival.n]
|
||||
(tset multival i (select i ...)))
|
||||
multival)
|
||||
|
||||
(fn nested-tset [t keys value]
|
||||
(let [next-key (. keys 1)]
|
||||
(if (= (length keys) 1) (tset t next-key value)
|
||||
(do (when (= (. t next-key) nil)
|
||||
(tset t next-key {}))
|
||||
(nested-tset (. t next-key) (lume.slice keys 2) value)))))
|
||||
|
||||
(fn file-exists [name]
|
||||
(let [f (io.open name :r)]
|
||||
(when (not= f nil) (io.close f))
|
||||
(not= f nil)))
|
||||
|
||||
(fn pairoff [l]
|
||||
(fn [_ iprev] (let [i (if iprev (+ iprev 2) 1)]
|
||||
(when (< i (length l)) (values i (. l i) (. l (+ i 1)))))))
|
||||
|
||||
(fn countiter [minmax ?max ?step]
|
||||
(let [min (if ?max minmax 1)
|
||||
max (or ?max minmax)
|
||||
step (or ?step 1)]
|
||||
(fn [_ iprev]
|
||||
(let [i (if iprev (+ iprev step) min)]
|
||||
(when (if (> step 0) (<= i max) (>= i max)) i)))))
|
||||
|
||||
(fn condlist [...] (let [l []] (lume.push l ...) l))
|
||||
|
||||
(fn prototype [base] (setmetatable {} {:__index base}))
|
||||
|
||||
{: splice : condlist : prototype
|
||||
: reload : hotswap : swappable :require swappable-require : hot-table : nested-tset : pairoff : countiter
|
||||
: readjson : writejson : file-exists : waitfor : in-coro : multival}
|
||||
|
Loading…
Reference in a new issue