Lua: huge API breaking change: converted all snake_case APIs to camelCase

pull/1232/head
Zef Hemel 2025-02-06 10:04:45 +01:00
parent 0635faabfc
commit a169406d37
42 changed files with 377 additions and 599 deletions

View File

@ -436,7 +436,7 @@ Deno.test("Thread local _CTX - advanced cases", async () => {
sf.threadLocal.setLocal("_GLOBAL", env); sf.threadLocal.setLocal("_GLOBAL", env);
assertEquals( assertEquals(
await evalExpr( await evalExpr(
"space_lua.interpolate('Hello, ${globalEnv} and ${loc}!', {loc='local'})", "spacelua.interpolate('Hello, ${globalEnv} and ${loc}!', {loc='local'})",
env, env,
sf, sf,
), ),
@ -446,7 +446,7 @@ Deno.test("Thread local _CTX - advanced cases", async () => {
// Some more complex string interpolation with more complex lua expressions, with nested {} // Some more complex string interpolation with more complex lua expressions, with nested {}
assertEquals( assertEquals(
await evalExpr( await evalExpr(
`space_lua.interpolate('Some JSON \${js.stringify(js.tojs({name="Pete"}))}!')`, `spacelua.interpolate('Some JSON \${js.stringify(js.tojs({name="Pete"}))}!')`,
env, env,
sf, sf,
), ),

View File

@ -15,7 +15,7 @@ import { stringApi } from "$common/space_lua/stdlib/string.ts";
import { tableApi } from "$common/space_lua/stdlib/table.ts"; import { tableApi } from "$common/space_lua/stdlib/table.ts";
import { osApi } from "$common/space_lua/stdlib/os.ts"; import { osApi } from "$common/space_lua/stdlib/os.ts";
import { jsApi } from "$common/space_lua/stdlib/js.ts"; import { jsApi } from "$common/space_lua/stdlib/js.ts";
import { spaceLuaApi } from "$common/space_lua/stdlib/space_lua.ts"; import { spaceluaApi } from "$common/space_lua/stdlib/space_lua.ts";
import { mathApi } from "$common/space_lua/stdlib/math.ts"; import { mathApi } from "$common/space_lua/stdlib/math.ts";
import { parse } from "$common/space_lua/parse.ts"; import { parse } from "$common/space_lua/parse.ts";
import { evalStatement } from "$common/space_lua/eval.ts"; import { evalStatement } from "$common/space_lua/eval.ts";
@ -193,7 +193,7 @@ export function luaBuildStandardEnv() {
env.set("math", mathApi); env.set("math", mathApi);
// Non-standard // Non-standard
env.set("each", eachFunction); env.set("each", eachFunction);
env.set("space_lua", spaceLuaApi); env.set("spacelua", spaceluaApi);
// env.set("template", templateApi); // env.set("template", templateApi);
return env; return env;
} }

View File

@ -32,7 +32,7 @@ export const jsApi = new LuaTable({
} }
return m; return m;
}), }),
each_iterable: new LuaBuiltinFunction((_sf, val) => { eachIterable: new LuaBuiltinFunction((_sf, val) => {
const iterator = val[Symbol.asyncIterator](); const iterator = val[Symbol.asyncIterator]();
return async () => { return async () => {
const result = await iterator.next(); const result = await iterator.next();

View File

@ -210,12 +210,12 @@ print(math.ult(2, 1)) -- prints: false
``` ```
# Non-standard Extensions # Non-standard Extensions
## math.cosine_similarity(vecA, vecB) ## math.cosineSimilarity(vecA, vecB)
Returns the cosine similarity between two vectors. Returns the cosine similarity between two vectors.
Example: Example:
```lua ```lua
local vec1 = {1, 2, 3} local vec1 = {1, 2, 3}
local vec2 = {4, 5, 6} local vec2 = {4, 5, 6}
print(math.cosine_similarity(vec1, vec2)) -- prints: 0.9746318461970762 print(math.cosineSimilarity(vec1, vec2)) -- prints: 0.9746318461970762
``` ```

View File

@ -71,8 +71,8 @@ export const mathApi = new LuaTable({
return (m >>> 0) < (n >>> 0); return (m >>> 0) < (n >>> 0);
}), }),
// Keep the cosine_similarity utility function // Keep the cosineSimilarity utility function
cosine_similarity: new LuaBuiltinFunction( cosineSimilarity: new LuaBuiltinFunction(
(sf, vecA: LuaTable | number[], vecB: LuaTable | number[]) => { (sf, vecA: LuaTable | number[], vecB: LuaTable | number[]) => {
// Convert LuaTable to number[] // Convert LuaTable to number[]
if (vecA instanceof LuaTable) { if (vecA instanceof LuaTable) {

View File

@ -1,48 +1,48 @@
local function assert_equal(a, b) local function assertEqual(a, b)
if a ~= b then if a ~= b then
error("Assertion failed: " .. a .. " is not equal to " .. b) error("Assertion failed: " .. a .. " is not equal to " .. b)
end end
end end
-- Trigonometric functions -- Trigonometric functions
assert_equal(math.cos(0), 1) assertEqual(math.cos(0), 1)
assert_equal(math.sin(0), 0) assertEqual(math.sin(0), 0)
assert_equal(math.tan(0), 0) assertEqual(math.tan(0), 0)
assert_equal(math.acos(1), 0) assertEqual(math.acos(1), 0)
assert_equal(math.asin(0), 0) assertEqual(math.asin(0), 0)
assert_equal(math.atan(0), 0) assertEqual(math.atan(0), 0)
-- Hyperbolic functions -- Hyperbolic functions
assert_equal(math.cosh(0), 1) assertEqual(math.cosh(0), 1)
assert_equal(math.sinh(0), 0) assertEqual(math.sinh(0), 0)
assert_equal(math.tanh(0), 0) assertEqual(math.tanh(0), 0)
-- Basic functions -- Basic functions
assert_equal(math.abs(-5), 5) assertEqual(math.abs(-5), 5)
assert_equal(math.ceil(3.3), 4) assertEqual(math.ceil(3.3), 4)
assert_equal(math.floor(3.7), 3) assertEqual(math.floor(3.7), 3)
assert_equal(math.max(1, 2, 3, 4), 4) assertEqual(math.max(1, 2, 3, 4), 4)
assert_equal(math.min(1, 2, 3, 4), 1) assertEqual(math.min(1, 2, 3, 4), 1)
-- Rounding and remainder -- Rounding and remainder
assert_equal(math.fmod(7, 3), 1) assertEqual(math.fmod(7, 3), 1)
-- Power and logarithms -- Power and logarithms
assert_equal(math.exp(0), 1) assertEqual(math.exp(0), 1)
assert_equal(math.log(math.exp(1)), 1) assertEqual(math.log(math.exp(1)), 1)
assert_equal(math.log(8, 2), 3) -- log base 2 of 8 assertEqual(math.log(8, 2), 3) -- log base 2 of 8
assert_equal(math.pow(2, 3), 8) assertEqual(math.pow(2, 3), 8)
assert_equal(math.sqrt(9), 3) assertEqual(math.sqrt(9), 3)
-- Random number tests (basic range checks) -- Random number tests (basic range checks)
local rand = math.random() local rand = math.random()
assert_equal(rand >= 0 and rand < 1, true) assertEqual(rand >= 0 and rand < 1, true)
local rand_n = math.random(10) local randN = math.random(10)
assert_equal(rand_n >= 1 and rand_n <= 10, true) assertEqual(randN >= 1 and randN <= 10, true)
local rand_range = math.random(5, 10) local randRange = math.random(5, 10)
assert_equal(rand_range >= 5 and rand_range <= 10, true) assertEqual(randRange >= 5 and randRange <= 10, true)
-- Unsigned less than comparison -- Unsigned less than comparison
assert_equal(math.ult(1, 2), true) assertEqual(math.ult(1, 2), true)
assert_equal(math.ult(2, 1), false) assertEqual(math.ult(2, 1), false)

View File

@ -100,7 +100,7 @@ export async function interpolateLuaString(
return result; return result;
} }
export const spaceLuaApi = new LuaTable({ export const spaceluaApi = new LuaTable({
/** /**
* Parses a lua expression and returns the parsed expression. * Parses a lua expression and returns the parsed expression.
* *
@ -108,7 +108,7 @@ export const spaceLuaApi = new LuaTable({
* @param luaExpression - The lua expression to parse. * @param luaExpression - The lua expression to parse.
* @returns The parsed expression. * @returns The parsed expression.
*/ */
parse_expression: new LuaBuiltinFunction( parseExpression: new LuaBuiltinFunction(
(_sf, luaExpression: string) => { (_sf, luaExpression: string) => {
return parseExpressionString(luaExpression); return parseExpressionString(luaExpression);
}, },
@ -121,7 +121,7 @@ export const spaceLuaApi = new LuaTable({
* @param envAugmentation - An optional environment to augment the global environment with. * @param envAugmentation - An optional environment to augment the global environment with.
* @returns The result of the evaluated expression. * @returns The result of the evaluated expression.
*/ */
eval_expression: new LuaBuiltinFunction( evalExpression: new LuaBuiltinFunction(
async (sf, parsedExpr: LuaExpression, envAugmentation?: LuaTable) => { async (sf, parsedExpr: LuaExpression, envAugmentation?: LuaTable) => {
const env = createAugmentedEnv(sf, envAugmentation); const env = createAugmentedEnv(sf, envAugmentation);
return luaValueToJS(await evalExpression(parsedExpr, env, sf)); return luaValueToJS(await evalExpression(parsedExpr, env, sf));
@ -138,7 +138,7 @@ export const spaceLuaApi = new LuaTable({
/** /**
* Returns your SilverBullet instance's base URL, or `undefined` when run on the server * Returns your SilverBullet instance's base URL, or `undefined` when run on the server
*/ */
base_url: new LuaBuiltinFunction( baseUrl: new LuaBuiltinFunction(
() => { () => {
// Deal with Deno // Deal with Deno
if (typeof location === "undefined") { if (typeof location === "undefined") {

View File

@ -1,15 +1,8 @@
local function assert_equal(a, b) local parsedExpr = spacelua.parseExpression("1 + 1")
if a ~= b then local evalResult = spacelua.evalExpression(parsedExpr)
error("Assertion failed: " .. a .. " is not equal to " .. b)
end
end
-- Test space_lua stuff
local parsedExpr = space_lua.parse_expression("1 + 1")
local evalResult = space_lua.eval_expression(parsedExpr)
assert(evalResult == 2, "Eval should return 2") assert(evalResult == 2, "Eval should return 2")
-- Slightly more advanced example with augmented environment -- Slightly more advanced example with augmented environment
local parsedExpr = space_lua.parse_expression("tostring(a + 1)") local parsedExpr = spacelua.parseExpression("tostring(a + 1)")
local evalResult = space_lua.eval_expression(parsedExpr, { a = 1 }) local evalResult = spacelua.evalExpression(parsedExpr, { a = 1 })
assert(evalResult == "2", "Eval should return 2 as a string") assert(evalResult == "2", "Eval should return 2 as a string")

View File

@ -207,18 +207,18 @@ export const stringApi = new LuaTable({
trim: new LuaBuiltinFunction((_sf, s: string) => { trim: new LuaBuiltinFunction((_sf, s: string) => {
return s.trim(); return s.trim();
}), }),
trim_start: new LuaBuiltinFunction((_sf, s: string) => { trimStart: new LuaBuiltinFunction((_sf, s: string) => {
return s.trimStart(); return s.trimStart();
}), }),
trim_end: new LuaBuiltinFunction((_sf, s: string) => { trimEnd: new LuaBuiltinFunction((_sf, s: string) => {
return s.trimEnd(); return s.trimEnd();
}), }),
match_regex: new LuaBuiltinFunction((_sf, s: string, pattern: string) => { matchRegex: new LuaBuiltinFunction((_sf, s: string, pattern: string) => {
const regex = new RegExp(pattern); const regex = new RegExp(pattern);
const result = s.match(regex); const result = s.match(regex);
return jsToLuaValue(result); return jsToLuaValue(result);
}), }),
match_regex_all: new LuaBuiltinFunction((_sf, s: string, pattern: string) => { matchRegexAll: new LuaBuiltinFunction((_sf, s: string, pattern: string) => {
const regex = new RegExp(pattern, "g"); const regex = new RegExp(pattern, "g");
return () => { return () => {
const match = regex.exec(s); const match = regex.exec(s);

View File

@ -1,4 +1,4 @@
local function assert_equal(a, b) local function assertEqual(a, b)
if a ~= b then if a ~= b then
error("Assertion failed: " .. a .. " is not equal to " .. b) error("Assertion failed: " .. a .. " is not equal to " .. b)
end end
@ -73,80 +73,80 @@ assert(result == "hello-world", "Magic character replacement failed")
-- Test string.match -- Test string.match
local m1, m2 = string.match("hello world", "(h)(ello)") local m1, m2 = string.match("hello world", "(h)(ello)")
assert_equal(m1, "h") assertEqual(m1, "h")
assert_equal(m2, "ello") assertEqual(m2, "ello")
-- Test match with init position - need to capture the group -- Test match with init position - need to capture the group
local init_match = string.match("hello world", "(world)", 7) local initMatch = string.match("hello world", "(world)", 7)
assert_equal(init_match, "world") assertEqual(initMatch, "world")
-- Test string.gmatch -- Test string.gmatch
local words = {} local words = {}
for word in string.gmatch("hello world lua", "%w+") do for word in string.gmatch("hello world lua", "%w+") do
table.insert(words, word) table.insert(words, word)
end end
assert_equal(words[1], "hello") assertEqual(words[1], "hello")
assert_equal(words[2], "world") assertEqual(words[2], "world")
assert_equal(words[3], "lua") assertEqual(words[3], "lua")
-- Test string.reverse -- Test string.reverse
assert_equal(string.reverse("hello"), "olleh") assertEqual(string.reverse("hello"), "olleh")
assert_equal(string.reverse(""), "") assertEqual(string.reverse(""), "")
-- Test string.split -- Test string.split
local parts = string.split("a,b,c", ",") local parts = string.split("a,b,c", ",")
assert_equal(parts[1], "a") assertEqual(parts[1], "a")
assert_equal(parts[2], "b") assertEqual(parts[2], "b")
assert_equal(parts[3], "c") assertEqual(parts[3], "c")
-- Test non-standard string extensions -- Test non-standard string extensions
assert_equal(string.startswith("hello world", "hello"), true) assertEqual(string.startswith("hello world", "hello"), true)
assert_equal(string.startswith("hello world", "world"), false) assertEqual(string.startswith("hello world", "world"), false)
assert_equal(string.endswith("hello world", "world"), true) assertEqual(string.endswith("hello world", "world"), true)
assert_equal(string.endswith("hello world", "hello"), false) assertEqual(string.endswith("hello world", "hello"), false)
-- Extended string.match tests -- Extended string.match tests
-- Basic pattern matching -- Basic pattern matching
assert_equal(string.match("hello", "h"), "h") assertEqual(string.match("hello", "h"), "h")
assert_equal(string.match("hello", "hello"), "hello") assertEqual(string.match("hello", "hello"), "hello")
-- Test with no matches -- Test with no matches
assert_equal(string.match("hello", "x"), nil) assertEqual(string.match("hello", "x"), nil)
-- Test with captures -- Test with captures
local m1, m2 = string.match("hello", "(h)(ello)") local m1, m2 = string.match("hello", "(h)(ello)")
assert_equal(m1, "h") assertEqual(m1, "h")
assert_equal(m2, "ello") assertEqual(m2, "ello")
-- Test with init position -- Test with init position
local init_match = string.match("hello world", "(world)", 7) local initMatch = string.match("hello world", "(world)", 7)
assert_equal(init_match, "world") assertEqual(initMatch, "world")
-- Test init position with no match -- Test init position with no match
assert_equal(string.match("hello world", "hello", 7), nil) assertEqual(string.match("hello world", "hello", 7), nil)
-- Test pattern characters -- Test pattern characters
assert_equal(string.match("123", "%d+"), "123") assertEqual(string.match("123", "%d+"), "123")
assert_equal(string.match("abc123", "%a+"), "abc") assertEqual(string.match("abc123", "%a+"), "abc")
assert_equal(string.match(" abc", "%s+"), " ") assertEqual(string.match(" abc", "%s+"), " ")
-- Test multiple captures -- Test multiple captures
local day, month, year = string.match("2024-03-14", "(%d+)-(%d+)-(%d+)") local day, month, year = string.match("2024-03-14", "(%d+)-(%d+)-(%d+)")
assert_equal(day, "2024") assertEqual(day, "2024")
assert_equal(month, "03") assertEqual(month, "03")
assert_equal(year, "14") assertEqual(year, "14")
-- Test optional captures -- Test optional captures
local word = string.match("The quick brown fox", "%s*(%w+)%s*") local word = string.match("The quick brown fox", "%s*(%w+)%s*")
assert_equal(word, "The") assertEqual(word, "The")
-- Test match_regex_all -- Test matchRegexAll
local matches = {} local matches = {}
for match in string.match_regex_all("hellolllbl", "(l+)") do for match in string.matchRegexAll("hellolllbl", "(l+)") do
table.insert(matches, match) table.insert(matches, match)
end end
assert_equal(#matches, 3) assertEqual(#matches, 3)
assert_equal(matches[1][1], "ll") assertEqual(matches[1][1], "ll")
assert_equal(matches[2][1], "lll") assertEqual(matches[2][1], "lll")
assert_equal(matches[3][1], "l") assertEqual(matches[3][1], "l")

View File

@ -34,9 +34,7 @@ function exposeSyscalls(env: LuaEnv, system: System<any>) {
const luaFn = new LuaNativeJSFunction((...args) => { const luaFn = new LuaNativeJSFunction((...args) => {
return system.localSyscall(syscallName, args); return system.localSyscall(syscallName, args);
}); });
// Register the function with the same name as the syscall both in regular and snake_case
env.get(ns, nativeFs).set(fn, luaFn, nativeFs); env.get(ns, nativeFs).set(fn, luaFn, nativeFs);
env.get(ns, nativeFs).set(snakeCase(fn), luaFn, nativeFs);
} }
} }
@ -66,9 +64,9 @@ export async function handleLuaError(e: LuaRuntimeError, system: System<any>) {
console.error( console.error(
"Lua eval exception", "Lua eval exception",
e.message, e.message,
e.sf.astCtx, e.sf?.astCtx,
); );
if (e.sf.astCtx && e.sf.astCtx.ref) { if (e.sf?.astCtx && e.sf.astCtx.ref) {
// We got an error and actually know where it came from, let's navigate there to help debugging // We got an error and actually know where it came from, let's navigate there to help debugging
const pageRef = parsePageRef(e.sf.astCtx.ref); const pageRef = parsePageRef(e.sf.astCtx.ref);
await system.localSyscall( await system.localSyscall(
@ -95,7 +93,3 @@ export async function handleLuaError(e: LuaRuntimeError, system: System<any>) {
]); ]);
} }
} }
function snakeCase(s: string) {
return s.replace(/([A-Z])/g, "_$1").toLowerCase();
}

View File

@ -46,7 +46,7 @@ export function commandSyscalls(
}, },
); );
}, },
"slash_command.define": ( "slashcommand.define": (
_ctx, _ctx,
def: CallbackCommandDef, def: CallbackCommandDef,
) => { ) => {

View File

@ -111,7 +111,7 @@ export function indexSyscalls(commonSystem: CommonSystem): SysCallMapping {
); );
} }
} }
return (await global.get("datastore").get("query_lua").call( return (await global.get("datastore").get("queryLua").call(
sf, sf,
[ [
"idx", "idx",

View File

@ -6,11 +6,11 @@ Config library for defining and getting config values
-- priority: 10 -- priority: 10
config = {} config = {}
local config_values = {} local configValues = {}
local config_schema = {} local configSchema = {}
function config.define(key, schema) function config.define(key, schema)
config_schema[key] = schema or true configSchema[key] = schema or true
end end
function config.set(keyOrTable, value) function config.set(keyOrTable, value)
@ -21,20 +21,19 @@ function config.set(keyOrTable, value)
return return
end end
local key = keyOrTable local key = keyOrTable
local schema = config_schema[key] local schema = configSchema[key]
if schema == nil then if schema == nil then
error("Config key not defined: " .. key) error("Config key not defined: " .. key)
end end
if schema != true then if schema != true then
local result = jsonschema.validate_object(schema, value) local result = jsonschema.validateObject(schema, value)
if result != nil then if result != nil then
error("Validation error (" .. key .. "): " .. result) error("Validation error (" .. key .. "): " .. result)
end end
end end
config_values[key] = value configValues[key] = value
end end
function config.get(key) function config.get(key)
return config_values[key] return configValues[key]
end end
```

View File

@ -7,28 +7,28 @@ Editor support for Lua, implemented in Lua. Of course.
local LUA_KEYWORDS = {"do", "if", "then", "for", "else", "end", "function", "local", "return"} local LUA_KEYWORDS = {"do", "if", "then", "for", "else", "end", "function", "local", "return"}
-- Are we in a comment? -- Are we in a comment?
local function in_comment(line) local function inComment(line)
return string.find(line, "--") return string.find(line, "--")
end end
-- Are we in a string? -- Are we in a string?
local function in_string(line) local function inString(line)
local single_quotes = 0 local singleQuotes = 0
local double_quotes = 0 local doubleQuotes = 0
local brackets = 0 local brackets = 0
for i = 1, string.len(line) do for i = 1, string.len(line) do
local c = line[i] local c = line[i]
if c == "'" then if c == "'" then
single_quotes = single_quotes + 1 singleQuotes = singleQuotes + 1
elseif c == '"' then elseif c == '"' then
double_quotes = double_quotes + 1 doubleQuotes = doubleQuotes + 1
elseif c == "[" and line[i+1] == "[" then elseif c == "[" and line[i+1] == "[" then
brackets = brackets + 1 brackets = brackets + 1
elseif c == "]" and line[i-1] == "]" then elseif c == "]" and line[i-1] == "]" then
brackets = brackets - 1 brackets = brackets - 1
end end
end end
return single_quotes % 2 == 1 or double_quotes % 2 == 1 or brackets > 0 return singleQuotes % 2 == 1 or doubleQuotes % 2 == 1 or brackets > 0
end end
-- API code completion for Lua -- API code completion for Lua
@ -37,33 +37,33 @@ event.listen {
name = "editor:complete", name = "editor:complete",
run = function(e) run = function(e)
local parents = e.data.parentNodes local parents = e.data.parentNodes
local found_space_lua = false local foundSpaceLua = false
for _, parent in ipairs(parents) do for _, parent in ipairs(parents) do
if string.startswith(parent, "FencedCode:space-lua") then if string.startswith(parent, "FencedCode:space-lua") then
found_space_lua = true foundSpaceLua = true
end end
end end
if not found_space_lua then if not foundSpaceLua then
return return
end end
local line_prefix = e.data.linePrefix local linePrefix = e.data.linePrefix
if in_comment(line_prefix) or in_string(line_prefix) then if inComment(linePrefix) or inString(linePrefix) then
return return
end end
local pos = e.data.pos local pos = e.data.pos
local propaccess_prefix = string.match_regex(line_prefix, "([a-zA-Z_0-9]+\\.)*([a-zA-Z_0-9]*)$") local propaccessPrefix = string.matchRegex(linePrefix, "([a-zA-Z_0-9]+\\.)*([a-zA-Z_0-9]*)$")
if not propaccess_prefix or not propaccess_prefix[1] then if not propaccessPrefix or not propaccessPrefix[1] then
-- No propaccess prefix, so we can't complete -- No propaccess prefix, so we can't complete
return return
end end
-- Split propaccess and traverse -- Split propaccess and traverse
local prop_parts = string.split(propaccess_prefix[1], ".") local propParts = string.split(propaccessPrefix[1], ".")
local current_value = _CTX._GLOBAL local currentValue = _CTX._GLOBAL
local failed = false local failed = false
for i = 1, #prop_parts-1 do for i = 1, #propParts-1 do
local prop = prop_parts[i] local prop = propParts[i]
if current_value then if currentValue then
current_value = current_value[prop] currentValue = currentValue[prop]
else else
failed = true failed = true
end end
@ -71,13 +71,13 @@ event.listen {
if failed then if failed then
return return
end end
local last_prop = prop_parts[#prop_parts] local lastProp = propParts[#propParts]
if table.includes(LUA_KEYWORDS, last_prop) then if table.includes(LUA_KEYWORDS, lastProp) then
return return
end end
local options = {} local options = {}
for key, val in pairs(current_value) do for key, val in pairs(currentValue) do
if string.startswith(key, last_prop) and val then if string.startswith(key, lastProp) and val then
if val.call then if val.call then
-- We got a function -- We got a function
if val.body then if val.body then
@ -106,7 +106,7 @@ event.listen {
end end
if #options > 0 then if #options > 0 then
return { return {
from = pos - string.len(last_prop), from = pos - string.len(lastProp),
options = options options = options
} }
end end
@ -118,34 +118,33 @@ event.listen {
Various useful slash templates. Various useful slash templates.
```space-lua ```space-lua
template.define_slash_command { template.defineSlashCommand {
name = "function", name = "function",
description = "Lua function", description = "Lua function",
only_contexts = {"FencedCode:space-lua"}, onlyContexts = {"FencedCode:space-lua"},
template = template.new [==[function |^|() template = template.new [==[function |^|()
end]==] end]==]
} }
template.define_slash_command { template.defineSlashCommand {
name = "tpl", name = "tpl",
description = "Lua template", description = "Lua template",
only_contexts = {"FencedCode:space-lua"}, onlyContexts = {"FencedCode:space-lua"},
template = template.new "template.new[==[|^|]==]" template = template.new "template.new[==[|^|]==]"
} }
template.define_slash_command { template.defineSlashCommand {
name = "lua-query", name = "lua-query",
description = "Lua query", description = "Lua query",
only_contexts = {"FencedCode:space-lua", "LuaDirective"}, onlyContexts = {"FencedCode:space-lua", "LuaDirective"},
template = template.new 'query[[from index.tag "|^|"]]' template = template.new 'query[[from index.tag "|^|"]]'
} }
-- A query embedded in ${} -- A query embedded in ${}
template.define_slash_command { template.defineSlashCommand {
name = "query", name = "query",
description = "Lua query", description = "Lua query",
except_contexts = {"FencedCode:space-lua", "LuaDirective"}, exceptContexts = {"FencedCode:space-lua", "LuaDirective"},
template = function() return '${query[[from index.tag "|^|"]]}' end template = function() return '${query[[from index.tag "|^|"]]}' end
} }
```

View File

@ -4,23 +4,23 @@ A work-in-progress library of generally useful templates for rendering queries.
```space-lua ```space-lua
-- Renders a page object as a linked list item -- Renders a page object as a linked list item
templates.page_item = template.new([==[ templates.pageItem = template.new([==[
* [[${name}]] * [[${name}]]
]==]) ]==])
-- Renders a task object as a togglable task -- Renders a task object as a togglable task
templates.task_item = template.new([==[ templates.taskItem = template.new([==[
* [${state}] [[${ref}]] ${name} * [${state}] [[${ref}]] ${name}
]==]) ]==])
``` ```
# Examples # Examples
`template.page_item`: `template.pageItem`:
${template.each(query[[from index.tag "page" limit 3]], templates.page_item)} ${template.each(query[[from index.tag "page" limit 3]], templates.pageItem)}
`template.task_item`: `template.taskItem`:
* [ ] Task 1 * [ ] Task 1
* [ ] Task 2 * [ ] Task 2
${template.each(query[[from index.tag "task" where page == _CTX.currentPage.name]], templates.task_item)} ${template.each(query[[from index.tag "task" where page == _CTX.currentPage.name]], templates.taskItem)}

View File

@ -20,42 +20,41 @@ function template.each(tbl, fn)
end end
-- Creates a new template function from a string template -- Creates a new template function from a string template
function template.new(template_str) function template.new(templateStr)
-- Preprocess: strip indentation -- Preprocess: strip indentation
local lines = {} local lines = {}
local split_lines = string.split(template_str, "\n") local splitLines = string.split(templateStr, "\n")
for _, line in ipairs(split_lines) do for _, line in ipairs(splitLines) do
line = string.gsub(line, "^ ", "") line = string.gsub(line, "^ ", "")
table.insert(lines, line) table.insert(lines, line)
end end
template_str = table.concat(lines, "\n") templateStr = table.concat(lines, "\n")
return function(obj) return function(obj)
return space_lua.interpolate(template_str, obj) return spacelua.interpolate(templateStr, obj)
end end
end end
-- Creates a template-based slash command, keys for def are: -- Creates a template-based slash command, keys for def are:
-- name: name of the slash command -- name: name of the slash command
-- description: description of the slash command -- description: description of the slash command
-- only_contexts: parent AST nodes in which this slash command is available, defaults to everywhere -- onlyContexts: parent AST nodes in which this slash command is available, defaults to everywhere
-- except_contexts: parent AST nodes in which this slash command is not available -- exceptContexts: parent AST nodes in which this slash command is not available
-- template: template function to apply -- template: template function to apply
-- insert_at: position to insert the template into -- insertAt: position to insert the template into
-- match: match string to apply the template to -- match: match string to apply the template to
-- match_regex: match regex to apply the template to -- matchRegex: match regex to apply the template to
function template.define_slash_command(def) function template.defineSlashCommand(def)
slash_command.define { slashcommand.define {
name = def.name, name = def.name,
description = def.description, description = def.description,
onlyContexts = def.only_contexts, onlyContexts = def.onlyContexts,
exceptContexts = def.except_contexts, exceptContexts = def.exceptContexts,
run = function() run = function()
system.invoke_function("template.applySnippetTemplate", def.template(), { system.invokeFunction("template.applySnippetTemplate", def.template(), {
insertAt = def.insert_at, insertAt = def.insertAt,
match = def.match, match = def.match,
matchRegex = def.match_regex matchRegex = def.matchRegex
}) })
end end
} }
end end
```

View File

@ -81,7 +81,7 @@ export function luaDirectivePlugin(client: Client) {
return result; return result;
} catch (e: any) { } catch (e: any) {
if (e instanceof LuaRuntimeError) { if (e instanceof LuaRuntimeError) {
if (e.sf.astCtx) { if (e.sf?.astCtx) {
const source = resolveASTReference(e.sf.astCtx); const source = resolveASTReference(e.sf.astCtx);
if (source) { if (source) {
// We know the origin node of the error, let's reference it // We know the origin node of the error, let's reference it

View File

@ -89,9 +89,9 @@ export class LuaWidget extends WidgetType {
html = widgetContent.html; html = widgetContent.html;
div.innerHTML = html; div.innerHTML = html;
if ((widgetContent as any)?.display === "block") { if ((widgetContent as any)?.display === "block") {
div.className = "sb-lua-directive-block"; div.className += " sb-lua-directive-block";
} else { } else {
div.className = "sb-lua-directive-inline"; div.className += " sb-lua-directive-inline";
} }
attachWidgetEventHandlers(div, this.client, this.from); attachWidgetEventHandlers(div, this.client, this.from);
this.client.setWidgetCache( this.client.setWidgetCache(

View File

@ -2,4 +2,4 @@ This describes the APIs available in [[Space Lua]]
${template.each(query[[ ${template.each(query[[
from index.tag "page" where string.startswith(name, "API/") from index.tag "page" where string.startswith(name, "API/")
]], templates.page_item)} ]], templates.pageItem)}

View File

@ -1,6 +1,5 @@
The Client Store API provides a simple key-value store for client-specific states and preferences. The Client Store API provides a simple key-value store for client-specific states and preferences.
## clientStore.set(key, value) ## clientStore.set(key, value)
Sets a value in the client store. Sets a value in the client store.

View File

@ -1,14 +1,24 @@
APIs related to editor commands APIs related to editor commands
### command.define(command_def) ### command.define(commandDef)
Registers a command. Registers a command.
Available keys:
* `name`: Name of the command
* `run`: Callback function
* `contexts`: AST node context in which this command should be available
* `priority`: Command priority (how high it appears in the list)
* `key`: Windows/Linux key binding (and mac, if not separately defined)
* `mac`: Mac-specific key binding
* `hide`: Hide this command from the [[Command Palette]]
* `requireMode`: `rw` or `ro` only enable this command in a particular mode (read-write, or read-only)
Example: Example:
```lua ```lua
command.define { command.define {
name = "My custom command", name = "My custom command",
run = function() run = function()
editor.flash_notification "Triggered my custom command" editor.flashNotification "Triggered my custom command"
end end
} }
```

View File

@ -1,4 +1,3 @@
The Datastore API provides functions for interacting with a key-value store that has query capabilities. The Datastore API provides functions for interacting with a key-value store that has query capabilities.
# Key-Value Operations # Key-Value Operations
@ -30,7 +29,7 @@ datastore.del("user:123")
# Batch Operations # Batch Operations
## datastore.batch_set(kvs) ## datastore.batchSet(kvs)
Sets multiple key-value pairs in a single operation. Sets multiple key-value pairs in a single operation.
Example: Example:
@ -39,27 +38,25 @@ local kvs = {
{key = "user:1", value = {name = "Alice"}}, {key = "user:1", value = {name = "Alice"}},
{key = "user:2", value = {name = "Bob"}} {key = "user:2", value = {name = "Bob"}}
} }
datastore.batch_set(kvs) datastore.batchSet(kvs)
``` ```
## datastore.batch_get(keys) ## datastore.batchGet(keys)
Gets multiple values in a single operation. Gets multiple values in a single operation.
Example: Example:
```lua ```lua
local keys = {"user:1", "user:2"} local keys = {"user:1", "user:2"}
local values = datastore.batch_get(keys) local values = datastore.batchGet(keys)
for _, value in ipairs(values) do for _, value in ipairs(values) do
print(value.name) print(value.name)
end end
``` ```
## datastore.batch_del(keys) ## datastore.batchDel(keys)
Deletes multiple values in a single operation. Deletes multiple values in a single operation.
Example: Example:
```lua ```lua
local keys = {"user:1", "user:2"} local keys = {"user:1", "user:2"}
datastore.batch_del(keys) datastore.batchDel(keys)
```

View File

@ -2,12 +2,12 @@
The Debug API provides functions for debugging and resetting the application state. The Debug API provides functions for debugging and resetting the application state.
## debug.reset_client() ## debug.resetClient()
Completely wipes the client state, including cached files and databases. Completely wipes the client state, including cached files and databases.
Example: Example:
```lua ```lua
debug.reset_client() debug.resetClient()
print("Client state has been reset") print("Client state has been reset")
``` ```
@ -18,4 +18,3 @@ Example:
```lua ```lua
debug.cleanup() debug.cleanup()
print("All KV stores have been wiped") print("All KV stores have been wiped")
```

View File

@ -1,280 +1,192 @@
The `editor` API provides functions for interacting with the editor interface, including text manipulation, cursor control, and UI operations. The Editor API provides functions for interacting with the editor interface.
## Page Operations ### editor.getCurrentPage()
### editor.get_current_page()
Returns the name of the page currently open in the editor. Returns the name of the page currently open in the editor.
Example: Example: ${editor.getCurrentPage()}
```lua
local page = editor.get_current_page()
print("Current page: " .. page)
```
### editor.get_current_page_meta() ### editor.getCurrentPageMeta()
Returns the meta data of the page currently open in the editor. Returns the meta data of the page currently open in the editor.
Example: Example:
```lua ${editor.getCurrentPageMeta()}
local meta = editor.get_current_page_meta()
print("Last modified: " .. meta.last_modified)
```
## Text Operations ### editor.getText()
### editor.get_text()
Returns the full text of the currently open page. Returns the full text of the currently open page.
Example: Example:
```lua ```lua
local text = editor.get_text() local text = editor.getText()
print("Document length: " .. #text) print("Document length: " .. #text)
``` ```
### editor.set_text(text, isolate_history) ### editor.setText(text, isolateHistory)
Updates the editor text while preserving cursor location. Updates the editor text while preserving cursor location.
Example: Example:
```lua ```lua
local text = editor.get_text() local text = editor.getText()
editor.set_text(text:upper(), false) -- Convert to uppercase editor.setText(text:upper(), false) -- Convert to uppercase
``` ```
### editor.insert_at_pos(text, pos) ### editor.insertAtPos(text, pos)
Insert text at the specified position. Insert text at the specified position.
Example: Example:
```lua ```lua
editor.insert_at_pos("Hello!", 0) -- Insert at beginning editor.insertAtPos("Hello!", 0) -- Insert at beginning
``` ```
### editor.replace_range(from, to, text) ### editor.replaceRange(from, to, text)
Replace text in the specified range. Replace text in the specified range.
Example: Example:
```lua ```lua
editor.replace_range(0, 5, "New text") editor.replaceRange(0, 5, "New text")
``` ```
### editor.insert_at_cursor(text) ### editor.insertAtCursor(text, scrollIntoView?)
Insert text at the current cursor position. Insert text at the current cursor position.
Example: Example:
```lua ```lua
editor.insert_at_cursor("Inserted at cursor") editor.insertAtCursor("Inserted at cursor")
``` ```
## Cursor Control ### editor.getCursor()
### editor.get_cursor()
Returns the cursor position as character offset. Returns the cursor position as character offset.
Example: Example:
```lua ```lua
local pos = editor.get_cursor() local pos = editor.getCursor()
print("Cursor at position: " .. pos) print("Cursor at position: " .. pos)
``` ```
### editor.get_selection() ### editor.getSelection()
Returns the current selection range. Returns the current selection range.
Example: Example:
```lua ```lua
local sel = editor.get_selection() local sel = editor.getSelection()
print("Selection from " .. sel.from .. " to " .. sel.to) print("Selection from " .. sel.from .. " to " .. sel.to)
``` ```
### editor.set_selection(from, to) ### editor.setSelection(from, to)
Sets the current selection range. Sets the current selection range.
Example: Example:
```lua ```lua
editor.set_selection(0, 10) -- Select first 10 characters editor.setSelection(0, 10) -- Select first 10 characters
``` ```
### editor.move_cursor(pos, center) ### editor.moveCursor(pos, center)
Move the cursor to a specific position. Move the cursor to a specific position.
Example: Example:
```lua ```lua
editor.move_cursor(0, true) -- Move to start and center editor.moveCursor(0, true) -- Move to start and center
``` ```
### editor.move_cursor_to_line(line, column, center) ### editor.moveCursorToLine(line, column, center)
Move the cursor to a specific line and column. Move the cursor to a specific line and column.
Example: Example:
```lua ```lua
editor.move_cursor_to_line(1, 1, true) -- Move to start of first line editor.moveCursorToLine(1, 1, true) -- Move to start of first line
``` ```
## Navigation ### editor.openPageNavigator(mode)
### editor.navigate(page_ref, replace_state, new_window)
Navigates to the specified page.
Example:
```lua
editor.navigate({page = "welcome"}, false, false)
```
### editor.open_page_navigator(mode)
Opens the page navigator. Opens the page navigator.
Example: Example:
```lua ```lua
editor.open_page_navigator("page") editor.openPageNavigator("page")
``` ```
### editor.open_command_palette() ### editor.openCommandPalette()
Opens the command palette. Opens the command palette.
Example: Example:
```lua ```lua
editor.open_command_palette() editor.openCommandPalette()
``` ```
## UI Operations ### editor.showPanel(id, mode, html, script)
### editor.show_panel(id, mode, html, script)
Shows a panel in the editor. Shows a panel in the editor.
Example: Example:
```lua ```lua
editor.show_panel("rhs", 1, "<h1>Hello</h1>") editor.showPanel("rhs", 1, "<h1>Hello</h1>")
``` ```
### editor.hide_panel(id) ### editor.hidePanel(id)
Hides a panel in the editor. Hides a panel in the editor.
Example: Example:
```lua ```lua
editor.hide_panel("rhs") editor.hidePanel("rhs")
``` ```
### editor.flash_notification(message, type) ### editor.flashNotification(message, type)
Shows a flash notification. Shows a flash notification.
Example: Example:
```lua ```lua
editor.flash_notification("Operation completed", "info") editor.flashNotification("Operation completed", "info")
``` ```
### editor.prompt(message, default_value) ### editor.downloadFile(filename, dataUrl)
Prompts the user for input.
Example:
```lua
local name = editor.prompt("Enter your name:", "")
print("Hello, " .. name)
```
### editor.confirm(message)
Shows a confirmation dialog.
Example:
```lua
if editor.confirm("Are you sure?") then
print("User confirmed")
end
```
## File Operations
### editor.download_file(filename, data_url)
Triggers a file download in the browser. Triggers a file download in the browser.
Example: Example:
```lua ```lua
editor.download_file("test.txt", "data:text/plain;base64,SGVsbG8=") editor.downloadFile("test.txt", "data:text/plain;base64,SGVsbG8=")
``` ```
### editor.upload_file(accept, capture) ### editor.uploadFile(accept, capture)
Opens a file upload dialog. Opens a file upload dialog.
Example: Example:
```lua ```lua
local file = editor.upload_file(".txt", nil) local file = editor.uploadFile(".txt", nil)
print("Uploaded: " .. file.name) print("Uploaded: " .. file.name)
``` ```
## Clipboard Operations ### editor.copyToClipboard(data)
### editor.copy_to_clipboard(data)
Copies data to the clipboard. Copies data to the clipboard.
Example: Example:
```lua ```lua
editor.copy_to_clipboard("Copied text") editor.copyToClipboard("Copied text")
``` ```
## Code Folding ### editor.toggleFold()
### editor.fold()
Folds code at the current cursor position.
Example:
```lua
editor.fold()
```
### editor.unfold()
Unfolds code at the current cursor position.
Example:
```lua
editor.unfold()
```
### editor.toggle_fold()
Toggles code folding at the current position. Toggles code folding at the current position.
Example: Example:
```lua ```lua
editor.toggle_fold() editor.toggleFold()
``` ```
### editor.fold_all() ### editor.foldAll()
Folds all foldable regions. Folds all foldable regions.
Example: Example:
```lua ```lua
editor.fold_all() editor.foldAll()
``` ```
### editor.unfold_all() ### editor.unfoldAll()
Unfolds all folded regions. Unfolds all folded regions.
Example: Example:
```lua ```lua
editor.unfold_all() editor.unfoldAll()
``` ```
## History Operations ### editor.openSearchPanel()
### editor.undo()
Undoes the last edit operation.
Example:
```lua
editor.undo()
```
### editor.redo()
Redoes the last undone operation.
Example:
```lua
editor.redo()
```
## Search Operations
### editor.open_search_panel()
Opens the editor's search panel. Opens the editor's search panel.
Example: Example:
```lua ```lua
editor.open_search_panel() editor.openSearchPanel()
```

View File

@ -4,7 +4,7 @@ The Event API provides functions for working with SilverBullet's event bus syste
## Event Operations ## Event Operations
### event.listen(listener_def) ### event.listen(listenerDef)
Register an event listener. Register an event listener.
```lua ```lua
@ -16,7 +16,7 @@ event.listen {
} }
``` ```
### event.dispatch(event_name, data, timeout) ### event.dispatch(eventName, data, timeout)
Triggers an event on the SilverBullet event bus. Event handlers can return values, which are accumulated and returned to the caller. Triggers an event on the SilverBullet event bus. Event handlers can return values, which are accumulated and returned to the caller.
Example: Example:
@ -25,19 +25,18 @@ Example:
event.dispatch("custom.event", {message = "Hello"}) event.dispatch("custom.event", {message = "Hello"})
-- Event dispatch with timeout and response handling -- Event dispatch with timeout and response handling
local responses = event.dispatch_event("data.request", {id = 123}, 5000) local responses = event.dispatchEvent("data.request", {id = 123}, 5000)
for _, response in ipairs(responses) do for _, response in ipairs(responses) do
print(response) print(response)
end end
``` ```
### event.list_events() ### event.listEvents()
Lists all events currently registered (listened to) on the SilverBullet event bus. Lists all events currently registered (listened to) on the SilverBullet event bus.
Example: Example:
```lua ```lua
local events = event.list_events() local events = event.listEvents()
for _, event_name in ipairs(events) do for _, eventName in ipairs(events) do
print("Registered event: " .. event_name) print("Registered event: " .. eventName)
end end
```

View File

@ -9,7 +9,7 @@ Example:
${query[[from index.tag("page") limit 1]]} ${query[[from index.tag("page") limit 1]]}
## index.index_objects(page, objects) ## index.indexObjects(page, objects)
Indexes an array of objects for a specific page. Indexes an array of objects for a specific page.
Example: Example:
@ -18,24 +18,23 @@ local objects = {
{tag = "mytask", ref="task1", content = "Buy groceries"}, {tag = "mytask", ref="task1", content = "Buy groceries"},
{tag = "mytask", ref="task2", content = "Write docs"} {tag = "mytask", ref="task2", content = "Write docs"}
} }
index.index_objects("my page", objects) index.indexObjects("my page", objects)
``` ```
## index.query_lua_objects(tag, query, scoped_variables?) ## index.queryLuaObjects(tag, query, scopedVariables?)
Queries objects using a Lua-based collection query. Queries objects using a Lua-based collection query.
Example: Example:
```lua ```lua
local tasks = index.query_lua_objects("mytask", {limit=3}) local tasks = index.queryLuaObjects("mytask", {limit=3})
``` ```
## index.get_object_by_ref(page, tag, ref) ## index.getObjectByRef(page, tag, ref)
Retrieves a specific object by its reference. Retrieves a specific object by its reference.
Example: Example:
```lua ```lua
local task = index.get_object_by_ref("my page", "mytask", "task1") local task = index.getObjectByRef("my page", "mytask", "task1")
if task then if task then
print("Found task: " .. task.content) print("Found task: " .. task.content)
end end
```

View File

@ -6,13 +6,13 @@ Imports a JavaScript module from a URL. Returns the imported module.
Example: Example:
```lua ```lua
-- Import lodash library -- Import lodash library
local lodash = js.import("https://esm.sh/lodash@4.17.21") local lodashLib = js.import("https://esm.sh/lodash@4.17.21")
local result = lodash.chunk({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3) local result = lodashLib.chunk({1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 3)
-- Import moment.js for date handling -- Import moment.js for date handling
local moment = js.import("https://esm.sh/moment@2.30.1") local momentLib = js.import("https://esm.sh/moment@2.30.1")
local day = moment("1995-12-25") local dateObj = momentLib("1995-12-25")
print(day.format("DD-MM-YYYY")) -- prints: 25-12-1995 print(dateObj.format("DD-MM-YYYY")) -- prints: 25-12-1995
``` ```
## js.new(constructor, ...) ## js.new(constructor, ...)
@ -20,8 +20,8 @@ Creates a new instance of a JavaScript class. Takes a constructor function and i
Example: Example:
```lua ```lua
local Date = js.import("https://esm.sh/date-fns") local DateClass = js.import("https://esm.sh/date-fns")
local date = js.new(Date, "2024-03-14") local dateObj = js.new(DateClass, "2024-03-14")
``` ```
## js.stringify(value) ## js.stringify(value)
@ -29,11 +29,11 @@ Converts a Lua value to a JSON string representation.
Example: Example:
```lua ```lua
local data = {1, 2, 3} local dataArray = {1, 2, 3}
print(js.stringify(data)) -- prints: [1,2,3] print(js.stringify(dataArray)) -- prints: [1,2,3]
local nested = lodash.chunk({1, 2, 3, 4, 5, 6}, 2) local nestedArray = lodashLib.chunk({1, 2, 3, 4, 5, 6}, 2)
print(js.stringify(nested)) -- prints: [[1,2],[3,4],[5,6]] print(js.stringify(nestedArray)) -- prints: [[1,2],[3,4],[5,6]]
``` ```
## js.tolua(value) ## js.tolua(value)
@ -63,13 +63,12 @@ js.log("Debug message")
js.log("User data:", {name = "John", age = 30}) js.log("User data:", {name = "John", age = 30})
``` ```
## js.each_iterable(iterable) ## js.eachIterable(iterable)
Creates an iterator for JavaScript async iterables. Creates an iterator for JavaScript async iterables.
Example: Example:
```lua ```lua
local async_iterator = js.each_iterable(some_js_async_iterable) local asyncIterator = js.eachIterable(someJsAsyncIterable)
for value in async_iterator do for value in asyncIterator do
print(value) print(value)
end end
```

View File

@ -4,7 +4,7 @@ The JSON Schema API provides functions for validating JSON objects against JSON
## Validation Operations ## Validation Operations
### jsonschema.validate_object(schema, object) ### jsonschema.validateObject(schema, object)
Validates a JSON object against a JSON schema. Validates a JSON object against a JSON schema.
Example: Example:
@ -19,7 +19,7 @@ local schema = {
} }
local object = {name = "John", age = 30} local object = {name = "John", age = 30}
local error = jsonschema.validate_object(schema, object) local error = jsonschema.validateObject(schema, object)
if error then if error then
print("Validation error: " .. error) print("Validation error: " .. error)
else else
@ -27,7 +27,7 @@ else
end end
``` ```
### jsonschema.validate_schema(schema) ### jsonschema.validateSchema(schema)
Validates a JSON schema itself to ensure it's well-formed. Validates a JSON schema itself to ensure it's well-formed.
Example: Example:
@ -39,10 +39,9 @@ local schema = {
} }
} }
local error = jsonschema.validate_schema(schema) local error = jsonschema.validateSchema(schema)
if error then if error then
print("Schema error: " .. error) print("Schema error: " .. error)
else else
print("Schema is valid") print("Schema is valid")
end end
```

View File

@ -2,7 +2,7 @@ The Language API provides functions for parsing code in various programming lang
## Language Operations ## Language Operations
### language.parse_language(language, code) ### language.parseLanguage(language, code)
Parses a piece of code using any of the supported SilverBullet languages. Parses a piece of code using any of the supported SilverBullet languages.
Example: Example:
@ -13,7 +13,7 @@ function hello() {
} }
]] ]]
local tree = language.parse_language("javascript", [[ local tree = language.parseLanguage("javascript", [[
function hello() { function hello() {
console.log("Hello, world!"); console.log("Hello, world!");
} }
@ -21,8 +21,8 @@ function hello() {
print("Parsed syntax tree:", tree) print("Parsed syntax tree:", tree)
``` ```
### language.list_languages() ### language.listLanguages()
Lists all supported languages in fenced code blocks. Lists all supported languages in fenced code blocks.
Example: Example:
${language.list_languages()} ${language.listLanguages()}

View File

@ -4,7 +4,7 @@ The Markdown API provides functions for parsing and rendering Markdown content.
## Markdown Operations ## Markdown Operations
### markdown.parse_markdown(text) ### markdown.parseMarkdown(text)
Parses a piece of markdown text into a ParseTree. Parses a piece of markdown text into a ParseTree.
Example: Example:
@ -15,18 +15,17 @@ local text = [[
This is a **bold** statement. This is a **bold** statement.
]] ]]
local tree = markdown.parse_markdown(text) local tree = markdown.parseMarkdown(text)
print("Parsed markdown tree:", tree) print("Parsed markdown tree:", tree)
``` ```
### markdown.render_parse_tree(tree) ### markdown.renderParseTree(tree)
Renders a ParseTree back to markdown text. Renders a ParseTree back to markdown text.
Example: Example:
```lua ```lua
local text = "# Title\n\nSome text" local text = "# Title\n\nSome text"
local tree = markdown.parse_markdown(text) local tree = markdown.parseMarkdown(text)
-- Modify tree if needed -- Modify tree if needed
local rendered = markdown.render_parse_tree(tree) local rendered = markdown.renderParseTree(tree)
print("Rendered markdown:", rendered) print("Rendered markdown:", rendered)
```

View File

@ -12,7 +12,7 @@ Example:
mq.send("tasks", {type = "process", data = "sample"}) mq.send("tasks", {type = "process", data = "sample"})
``` ```
### mq.batch_send(queue, bodies) ### mq.batchSend(queue, bodies)
Sends multiple messages to a queue in a single operation. Sends multiple messages to a queue in a single operation.
Example: Example:
@ -21,7 +21,7 @@ local messages = {
{type = "task1", data = "sample1"}, {type = "task1", data = "sample1"},
{type = "task2", data = "sample2"} {type = "task2", data = "sample2"}
} }
mq.batch_send("tasks", messages) mq.batchSend("tasks", messages)
``` ```
### mq.ack(queue, id) ### mq.ack(queue, id)
@ -32,23 +32,22 @@ Example:
mq.ack("tasks", "message-123") mq.ack("tasks", "message-123")
``` ```
### mq.batch_ack(queue, ids) ### mq.batchAck(queue, ids)
Acknowledges multiple messages from a queue in a single operation. Acknowledges multiple messages from a queue in a single operation.
Example: Example:
```lua ```lua
local messageIds = {"msg1", "msg2", "msg3"} local messageIds = {"msg1", "msg2", "msg3"}
mq.batch_ack("tasks", messageIds) mq.batchAck("tasks", messageIds)
``` ```
## Queue Management ## Queue Management
### mq.get_queue_stats(queue) ### mq.getQueueStats(queue)
Retrieves statistics about a particular queue. Retrieves statistics about a particular queue.
Example: Example:
```lua ```lua
local stats = mq.get_queue_stats("tasks") local stats = mq.getQueueStats("tasks")
print("Queue size: " .. stats.size) print("Queue size: " .. stats.size)
print("Processing: " .. stats.processing) print("Processing: " .. stats.processing)
```

View File

@ -2,149 +2,148 @@ The Space API provides functions for interacting with pages, attachments, and fi
# Page Operations # Page Operations
## space.list_pages() ## space.listPages()
Returns a list of all pages in the space. Returns a list of all pages in the space.
Example: Example:
```lua ```lua
local pages = space.list_pages() local pages = space.listPages()
for page in each(pages) do for page in each(pages) do
print(page.name) print(page.name)
end end
``` ```
## space.read_page(name) ## space.readPage(name)
Reads the content of a page. Reads the content of a page.
Example: Example:
```lua ```lua
local content = space.read_page("welcome") local content = space.readPage("welcome")
print(content) -- prints the content of the "welcome" page print(content) -- prints the content of the "welcome" page
``` ```
## space.get_page_meta(name) ## space.getPageMeta(name)
Gets metadata for a specific page. Gets metadata for a specific page.
Example: Example:
```lua ```lua
local meta = space.get_page_meta("welcome") local meta = space.getPageMeta("welcome")
print(meta.name, meta.lastModified) -- prints page name and last modified date print(meta.name, meta.lastModified) -- prints page name and last modified date
``` ```
## space.write_page(name, text) ## space.writePage(name, text)
Writes content to a page. Writes content to a page.
Example: Example:
```lua ```lua
local meta = space.write_page("notes", "My new note content") local meta = space.writePage("notes", "My new note content")
print("Page updated at: " .. meta.lastModified) print("Page updated at: " .. meta.lastModified)
``` ```
## space.delete_page(name) ## space.deletePage(name)
Deletes a page from the space. Deletes a page from the space.
Example: Example:
```lua ```lua
space.delete_page("old-notes") space.deletePage("old-notes")
``` ```
# Attachment Operations # Attachment Operations
## space.list_attachments() ## space.listAttachments()
Returns a list of all attachments in the space. Returns a list of all attachments in the space.
Example: Example:
```lua ```lua
local attachments = space.list_attachments() local attachments = space.listAttachments()
for att in each(attachments) do for att in each(attachments) do
print(att.name, att.size) print(att.name, att.size)
end end
``` ```
## space.read_attachment(name) ## space.readAttachment(name)
Reads the content of an attachment. Reads the content of an attachment.
Example: Example:
```lua ```lua
local data = space.read_attachment("image.png") local data = space.readAttachment("image.png")
print("Attachment size: " .. #data .. " bytes") print("Attachment size: " .. #data .. " bytes")
``` ```
## space.write_attachment(name, data) ## space.writeAttachment(name, data)
Writes binary data to an attachment. Writes binary data to an attachment.
Example: Example:
```lua ```lua
local binary_data = string.char(72, 69, 76, 76, 79) -- "HELLO" in binary local binaryData = string.char(72, 69, 76, 76, 79) -- "HELLO" in binary
local meta = space.write_attachment("test.bin", binary_data) local meta = space.writeAttachment("test.bin", binaryData)
print("Attachment saved with size: " .. meta.size) print("Attachment saved with size: " .. meta.size)
``` ```
## space.delete_attachment(name) ## space.deleteAttachment(name)
Deletes an attachment from the space. Deletes an attachment from the space.
Example: Example:
```lua ```lua
space.delete_attachment("old-image.png") space.deleteAttachment("old-image.png")
``` ```
# File Operations # File Operations
## space.list_files() ## space.listFiles()
Returns a list of all files in the space. Returns a list of all files in the space.
Example: Example:
```lua ```lua
local files = space.list_files() local files = space.listFiles()
for _, file in ipairs(files) do for _, file in ipairs(files) do
print(file.name, file.size) print(file.name, file.size)
end end
``` ```
## space.get_file_meta(name) ## space.getFileMeta(name)
Gets metadata for a specific file. Gets metadata for a specific file.
Example: Example:
```lua ```lua
local meta = space.get_file_meta("document.txt") local meta = space.getFileMeta("document.txt")
print(meta.name, meta.modified, meta.size) print(meta.name, meta.modified, meta.size)
``` ```
## space.read_file(name) ## space.readFile(name)
Reads the content of a file. Reads the content of a file.
Example: Example:
```lua ```lua
local content = space.read_file("document.txt") local content = space.readFile("document.txt")
print("File size: " .. #content .. " bytes") print("File size: " .. #content .. " bytes")
``` ```
## space.write_file(name, data) ## space.writeFile(name, data)
Writes binary data to a file. Writes binary data to a file.
Example: Example:
```lua ```lua
local text = "Hello, World!" local text = "Hello, World!"
local meta = space.write_file("greeting.txt", text) local meta = space.writeFile("greeting.txt", text)
print("File written with size: " .. meta.size) print("File written with size: " .. meta.size)
``` ```
## space.delete_file(name) ## space.deleteFile(name)
Deletes a file from the space. Deletes a file from the space.
Example: Example:
```lua ```lua
space.delete_file("old-document.txt") space.deleteFile("old-document.txt")
``` ```
## space.file_exists(name) ## space.fileExists(name)
Checks if a file exists in the space. Checks if a file exists in the space.
Example: Example:
```lua ```lua
if space.file_exists("config.json") then if space.fileExists("config.json") then
print("Config file exists!") print("Config file exists!")
else else
print("Config file not found") print("Config file not found")
end end
```

View File

@ -1,21 +0,0 @@
Space Lua specific functions that are available to all scripts, but are not part of the standard Lua language.
## space_lua.parse_expression(luaExpression)
Parses a lua expression and returns the parsed expression as an AST.
Example:
space_lua.parse_expression("1 + 1")
## space_lua.eval_expression(parsedExpr, envAugmentation?)
Evaluates a parsed Lua expression and returns the result. Optionally accepts an environment table to augment the global environment.
Example:
${space_lua.eval_expression(space_lua.parse_expression("x + y"), {x = 1, y = 2})}
## space_lua.interpolate(template, envAugmentation?)
Interpolates a string with lua expressions and returns the result. Expressions are wrapped in ${...} syntax. Optionally accepts an environment table to augment the global environment.
${space_lua.interpolate("Hello ${name}!", {name="Pete"})}

21
website/API/spacelua.md Normal file
View File

@ -0,0 +1,21 @@
The Space Lua API provides functions for working with Lua expressions and templates.
## spacelua.parseExpression(luaExpression)
Parses a lua expression and returns the parsed expression as an AST.
Example:
```lua
local parsedExpression = spacelua.parseExpression("1 + 1")
```
## spacelua.evalExpression(parsedExpr, envAugmentation?)
Evaluates a parsed Lua expression and returns the result. Optionally accepts an environment table to augment the global environment.
Example:
${spacelua.evalExpression(spacelua.parseExpression("x + y"), {x = 1, y = 2})}
## spacelua.interpolate(template, envAugmentation?)
Interpolates a string with lua expressions and returns the result. Expressions are wrapped in ${...} syntax. Optionally accepts an environment table to augment the global environment.
Example:
${spacelua.interpolate("Hello ${name}!", {name="Pete"})}

View File

@ -4,41 +4,40 @@ The Sync API provides functions for interacting with the sync engine when the cl
## Sync Operations ## Sync Operations
### sync.is_syncing() ### sync.isSyncing()
Checks if a sync is currently in progress. Checks if a sync is currently in progress.
Example: Example:
```lua ```lua
if sync.is_syncing() then if sync.isSyncing() then
print("Sync in progress...") print("Sync in progress...")
end end
``` ```
### sync.has_initial_sync_completed() ### sync.hasInitialSyncCompleted()
Checks if an initial sync has completed. Checks if an initial sync has completed.
Example: Example:
```lua ```lua
if sync.has_initial_sync_completed() then if sync.hasInitialSyncCompleted() then
print("Initial sync completed") print("Initial sync completed")
else else
print("Waiting for initial sync...") print("Waiting for initial sync...")
end end
``` ```
### sync.schedule_file_sync(path) ### sync.scheduleFileSync(path)
Actively schedules a file to be synced. Sync will happen by default too, but this prioritizes the file. Actively schedules a file to be synced. Sync will happen by default too, but this prioritizes the file.
Example: Example:
```lua ```lua
sync.schedule_file_sync("notes/important.md") sync.scheduleFileSync("notes/important.md")
``` ```
### sync.schedule_space_sync() ### sync.scheduleSpaceSync()
Schedules a sync without waiting for the usual sync interval. Schedules a sync without waiting for the usual sync interval.
Example: Example:
```lua ```lua
local changes = sync.schedule_space_sync() local changes = sync.scheduleSpaceSync()
print("Number of changes synced: " .. changes) print("Number of changes synced: " .. changes)
```

View File

@ -4,114 +4,113 @@ The System API provides system-level functions for interacting with the SilverBu
## Function Operations ## Function Operations
### system.invoke_function(name, ...) ### system.invokeFunction(name, ...)
Invokes a plug function by name. Invokes a plug function by name.
Example: Example:
```lua ```lua
-- Invoke a function from a plug -- Invoke a function from a plug
system.invoke_function("myplug.process_data", "input", 123) system.invokeFunction("myplug.processData", "input", 123)
``` ```
### system.invoke_command(name, args) ### system.invokeCommand(name, args)
Invokes a client command by name. Invokes a client command by name.
Example: Example:
```lua ```lua
system.invoke_command("editor.save", {}) system.invokeCommand("editor.save", {})
``` ```
### system.invoke_space_function(name, ...) ### system.invokeSpaceFunction(name, ...)
Invokes a space function by name. Invokes a space function by name.
Example: Example:
```lua ```lua
local result = system.invoke_space_function("custom_function", "arg1", "arg2") local result = system.invokeSpaceFunction("customFunction", "arg1", "arg2")
print("Function result:", result) print("Function result:", result)
``` ```
## System Information ## System Information
### system.list_commands() ### system.listCommands()
Lists all available commands. Lists all available commands.
Example: Example:
```lua ```lua
local commands = system.list_commands() local commands = system.listCommands()
for name, def in pairs(commands) do for name, def in pairs(commands) do
print(name .. ": " .. def.description) print(name .. ": " .. def.description)
end end
``` ```
### system.list_syscalls() ### system.listSyscalls()
Lists all available syscalls. Lists all available syscalls.
Example: Example:
```lua ```lua
local syscalls = system.list_syscalls() local syscalls = system.listSyscalls()
for _, syscall in ipairs(syscalls) do for _, syscall in ipairs(syscalls) do
print(syscall.name) print(syscall.name)
end end
``` ```
### system.get_env() ### system.getEnv()
Returns the runtime environment ("server", "client", or undefined for hybrid). Returns the runtime environment ("server", "client", or undefined for hybrid).
Example: Example:
```lua ```lua
local env = system.get_env() local env = system.getEnv()
print("Running in environment: " .. (env or "hybrid")) print("Running in environment: " .. (env or "hybrid"))
``` ```
### system.get_mode() ### system.getMode()
Returns the current mode of the system ("ro" or "rw"). Returns the current mode of the system ("ro" or "rw").
Example: Example:
```lua ```lua
local mode = system.get_mode() local mode = system.getMode()
print("System mode: " .. mode) print("System mode: " .. mode)
``` ```
### system.get_version() ### system.getVersion()
Returns the SilverBullet version. Returns the SilverBullet version.
Example: Example:
```lua ```lua
local version = system.get_version() local version = system.getVersion()
print("SilverBullet version: " .. version) print("SilverBullet version: " .. version)
``` ```
## Configuration ## Configuration
### system.get_space_config(key, default_value) ### system.getSpaceConfig(key, defaultValue)
Loads space configuration values. Loads space configuration values.
Example: Example:
```lua ```lua
-- Get specific config value -- Get specific config value
local value = system.get_space_config("theme", "light") local value = system.getSpaceConfig("theme", "light")
-- Get all config values -- Get all config values
local config = system.get_space_config() local config = system.getSpaceConfig()
for key, value in pairs(config) do for key, value in pairs(config) do
print(key .. ": " .. value) print(key .. ": " .. value)
end end
``` ```
### system.reload_config() ### system.reloadConfig()
Triggers an explicit reload of the configuration. Triggers an explicit reload of the configuration.
Example: Example:
```lua ```lua
local new_config = system.reload_config() system.reloadConfig()
print("Configuration reloaded") print("Configuration reloaded")
``` ```
### system.reload_plugs() ### system.reloadPlugs()
Triggers a reload of all plugs. Triggers a reload of all plugs.
Example: Example:
```lua ```lua
system.reload_plugs() system.reloadPlugs()
print("All plugs reloaded") print("All plugs reloaded")
```

View File

@ -8,10 +8,10 @@ Example:
```space-lua ```space-lua
examples = examples or {} examples = examples or {}
examples.say_hello = template.new[==[Hello ${name}!]==] examples.sayHello = template.new[==[Hello ${name}!]==]
``` ```
And its use: ${examples.say_hello {name="Pete"}} And its use: ${examples.sayHello {name="Pete"}}
## template.each(collection, template) ## template.each(collection, template)
Iterates over a collection and renders a template for each item. Iterates over a collection and renders a template for each item.

View File

@ -29,6 +29,6 @@ local data = {
hobbies = {"reading", "hiking"} hobbies = {"reading", "hiking"}
} }
local yaml_text = yaml.stringify(data) local yamlText = yaml.stringify(data)
print(yaml_text) print(yamlText)
``` ```

View File

@ -5,6 +5,7 @@ An attempt at documenting the changes/new features introduced in each release.
## Edge ## Edge
_These features are not yet properly released, you need to use [the edge builds](https://community.silverbullet.md/t/living-on-the-edge-builds/27) to try them._ _These features are not yet properly released, you need to use [the edge builds](https://community.silverbullet.md/t/living-on-the-edge-builds/27) to try them._
* **Lua Breaking change**: Converted all custom (non-standard) Lua APIs from snake_case to camelCase. This will likely result in a lot of `calling nil as function` style errors, but it's a necessary step to make the API more consistent and easier to use. Also, error reporting should now be better, and often directly navigate to the place in the code where the error occurred.
* (Security) Implemented a lockout mechanism after a number of failed login attempts for [[Authentication]] (configured via [[Install/Configuration#Authentication]]) (by [Peter Weston](https://github.com/silverbulletmd/silverbullet/pull/1152)) * (Security) Implemented a lockout mechanism after a number of failed login attempts for [[Authentication]] (configured via [[Install/Configuration#Authentication]]) (by [Peter Weston](https://github.com/silverbulletmd/silverbullet/pull/1152))
## 0.10.1 ## 0.10.1
@ -405,4 +406,3 @@ Other notable changes:
* [BLOCKED] A task thats blocked * [BLOCKED] A task thats blocked
[[Plugs/Tasks|Read more]] [[Plugs/Tasks|Read more]]
* Removed [[Cloud Links]] support in favor of [[Federation]]. If you still have legacy cloud links, simply replace the 🌩️ with a `!` and things should work as before. * Removed [[Cloud Links]] support in favor of [[Federation]]. If you still have legacy cloud links, simply replace the 🌩️ with a `!` and things should work as before.

View File

@ -1,70 +0,0 @@
#meta
# Configuration
Create a [[SECRETS]] page in your space, with a YAML block:
```yaml
OPENAI_API_KEY: yourapikeyhere
```
# Implementation
```space-lua
openai = {
Client = {}
}
openai.Client.__index = openai.Client
-- Create a new OpenAI client instance
function openai.Client.new(apiKey)
-- Read SECRETS if no API key provided
if not apiKey then
local secretsPage = space.readPage("SECRETS")
apiKey = string.match(secretsPage, "OPENAI_API_KEY: (%S+)")
end
if not apiKey then
error("No OpenAI API key supplied")
end
local openai_lib = js.import("https://esm.sh/openai")
local client = js.new(openai_lib.OpenAI, {
apiKey = apiKey,
dangerouslyAllowBrowser = true
})
local self = setmetatable({
client = client
}, OpenAIClient)
return self
end
-- Chat completion method
function openai.Client:chat(message)
local r = self.client.chat.completions.create({
model = "gpt-4o-mini",
messages = {
{ role = "user", content = message },
},
})
return r.choices[1].message.content
end
-- Streaming chat completion method
function openai.Client:stream_chat(message)
local r = self.client.chat.completions.create({
model = "gpt-4o-mini",
messages = {
{ role = "user", content = message },
},
stream = true,
})
local iterator = js.each_iterable(r)
return function()
local el = iterator()
if el then
return el.choices[1].delta.content
end
end
end
```

View File

@ -12,7 +12,7 @@ The introduction of Lua aims to unify and simplify a few SilverBullet features,
* Replace [[Expression Language]], [[Template Language]] and [[Query Language]] with Lua-based equivalents. * Replace [[Expression Language]], [[Template Language]] and [[Query Language]] with Lua-based equivalents.
* (Potentially) provide an alternative way to specify [[Space Config]] * (Potentially) provide an alternative way to specify [[Space Config]]
# Introduction approach # Strategy
This is a big effort. During its development, Space Lua will be offered as a kind of “alternative universe” to the things mentioned above. Existing [[Live Templates]], [[Live Queries]] and [[Space Script]] will continue to work as before, unaltered. This is a big effort. During its development, Space Lua will be offered as a kind of “alternative universe” to the things mentioned above. Existing [[Live Templates]], [[Live Queries]] and [[Space Script]] will continue to work as before, unaltered.
Once these features stabilize and best practices are ironed out, old mechanisms will likely be deprecated and possibly removed at some point. Once these features stabilize and best practices are ironed out, old mechanisms will likely be deprecated and possibly removed at some point.
@ -101,13 +101,13 @@ ${marquee "Finally, marqeeeeeeee!"}
Oh boy, the times we live in! Oh boy, the times we live in!
## Commands ## Commands
Custom commands can be defined using `command.define`: Custom commands can be defined using [[API/command#command.define(commandDef)]]:
```space-lua ```space-lua
command.define { command.define {
name = "Hello World", name = "Hello World",
run = function() run = function()
editor.flash_notification "Hello world!" editor.flashNotification "Hello world!"
event.dispatch("my-custom-event", {name="Pete"}) event.dispatch("my-custom-event", {name="Pete"})
end end
} }
@ -116,13 +116,13 @@ command.define {
Try it: {[Hello World]} Try it: {[Hello World]}
## Event listeners ## Event listeners
You can listen to events using `event.listen`: You can listen to events using [[API/event#event.listen(listenerDef)]]:
```space-lua ```space-lua
event.listen { event.listen {
name = "my-custom-event"; name = "my-custom-event";
run = function(e) run = function(e)
editor.flash_notification("Custom triggered: " editor.flashNotification("Custom triggered: "
.. e.data.name) .. e.data.name)
end end
} }
@ -135,64 +135,20 @@ Space Lua currently introduces a few new features on top core Lua:
2. Thread locals 2. Thread locals
## Thread locals ## Thread locals
Theres a magic `_CTX` global variable available from which you can access useful context-specific values. Currently the following keys are available: There's a magic `_CTX` global variable available from which you can access useful context-specific values. Currently the following keys are available:
* `_CTX.currentPage` providing access (in the client only) to the currently open page (PageMeta object) * `_CTX.currentPage` providing access (in the client only) to the currently open page (PageMeta object)
* `_CTX._GLOBAL` providing access to the global scope * `_CTX._GLOBAL` providing access to the global scope
# API # API
Lua APIs, which should be (roughly) implemented according to the Lua standard. All of these are available via the global namespace:
* `print` ${template.each(query[[from index.tag "page" where string.startswith(name, "API/")]], templates.pageItem)}
* `assert`
* `ipairs`
* `pairs`
* `unpack`
* `type`
* `tostring`
* `tonumber`
* `error`
* `pcall`
* `xpcall`
* `setmetatable`
* `getmetatable`
* `rawset`
* `string`:
* `byte`
* `char`
* `find`
* `format`
* `gmatch`
* `gsub`
* `len`
* `lower`
* `upper`
* `match`
* `rep`
* `reverse`
* `sub`
* `split`
* `table`
* `concat`
* `insert`
* `remove`
* `sort`
* `os`
* `time`
* `date`
* `js` (Warning: this will be revised): JavaScript interop functions
* `new`: instantiate a JavaScript constructor
* `importModule`: import a JavaScript from a URL (`import` equivalent)
* `tolua`: convert a JS value to a Lua value
* `tojs`: convert a Lua value to a JS value
* `log`: console.log
In addition, [all SilverBullet syscalls](https://jsr.io/@silverbulletmd/silverbullet/doc/syscalls) are exposed. However since the Lua naming convention prefers using `snake_case` it is recommended you call them that way. For instance: `editor.flash_notification` is more Luay than `editor.flashNotification` (although both are supported at this time -- again, subject to change).
While in [[Space Script]] all syscalls are asynchronous and need to be called with `await`, this is happens transparently in Space Lua leading to cleaner code: While in [[Space Script]] all syscalls are asynchronous and need to be called with `await`, this is happens transparently in Space Lua leading to cleaner code:
```space-lua ```space-lua
local function call_some_things() local function callSomeThings()
local text = space.read_page(editor.get_current_page()) local text = space.readPage(editor.getCurrentPage())
print("Current page text", text) print("Current page text", text)
end end
``` ```