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);
assertEquals(
await evalExpr(
"space_lua.interpolate('Hello, ${globalEnv} and ${loc}!', {loc='local'})",
"spacelua.interpolate('Hello, ${globalEnv} and ${loc}!', {loc='local'})",
env,
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 {}
assertEquals(
await evalExpr(
`space_lua.interpolate('Some JSON \${js.stringify(js.tojs({name="Pete"}))}!')`,
`spacelua.interpolate('Some JSON \${js.stringify(js.tojs({name="Pete"}))}!')`,
env,
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 { osApi } from "$common/space_lua/stdlib/os.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 { parse } from "$common/space_lua/parse.ts";
import { evalStatement } from "$common/space_lua/eval.ts";
@ -193,7 +193,7 @@ export function luaBuildStandardEnv() {
env.set("math", mathApi);
// Non-standard
env.set("each", eachFunction);
env.set("space_lua", spaceLuaApi);
env.set("spacelua", spaceluaApi);
// env.set("template", templateApi);
return env;
}

View File

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

View File

@ -210,12 +210,12 @@ print(math.ult(2, 1)) -- prints: false
```
# Non-standard Extensions
## math.cosine_similarity(vecA, vecB)
## math.cosineSimilarity(vecA, vecB)
Returns the cosine similarity between two vectors.
Example:
```lua
local vec1 = {1, 2, 3}
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);
}),
// Keep the cosine_similarity utility function
cosine_similarity: new LuaBuiltinFunction(
// Keep the cosineSimilarity utility function
cosineSimilarity: new LuaBuiltinFunction(
(sf, vecA: LuaTable | number[], vecB: LuaTable | number[]) => {
// Convert LuaTable to number[]
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
error("Assertion failed: " .. a .. " is not equal to " .. b)
error("Assertion failed: " .. a .. " is not equal to " .. b)
end
end
-- Trigonometric functions
assert_equal(math.cos(0), 1)
assert_equal(math.sin(0), 0)
assert_equal(math.tan(0), 0)
assert_equal(math.acos(1), 0)
assert_equal(math.asin(0), 0)
assert_equal(math.atan(0), 0)
assertEqual(math.cos(0), 1)
assertEqual(math.sin(0), 0)
assertEqual(math.tan(0), 0)
assertEqual(math.acos(1), 0)
assertEqual(math.asin(0), 0)
assertEqual(math.atan(0), 0)
-- Hyperbolic functions
assert_equal(math.cosh(0), 1)
assert_equal(math.sinh(0), 0)
assert_equal(math.tanh(0), 0)
assertEqual(math.cosh(0), 1)
assertEqual(math.sinh(0), 0)
assertEqual(math.tanh(0), 0)
-- Basic functions
assert_equal(math.abs(-5), 5)
assert_equal(math.ceil(3.3), 4)
assert_equal(math.floor(3.7), 3)
assert_equal(math.max(1, 2, 3, 4), 4)
assert_equal(math.min(1, 2, 3, 4), 1)
assertEqual(math.abs(-5), 5)
assertEqual(math.ceil(3.3), 4)
assertEqual(math.floor(3.7), 3)
assertEqual(math.max(1, 2, 3, 4), 4)
assertEqual(math.min(1, 2, 3, 4), 1)
-- Rounding and remainder
assert_equal(math.fmod(7, 3), 1)
assertEqual(math.fmod(7, 3), 1)
-- Power and logarithms
assert_equal(math.exp(0), 1)
assert_equal(math.log(math.exp(1)), 1)
assert_equal(math.log(8, 2), 3) -- log base 2 of 8
assert_equal(math.pow(2, 3), 8)
assert_equal(math.sqrt(9), 3)
assertEqual(math.exp(0), 1)
assertEqual(math.log(math.exp(1)), 1)
assertEqual(math.log(8, 2), 3) -- log base 2 of 8
assertEqual(math.pow(2, 3), 8)
assertEqual(math.sqrt(9), 3)
-- Random number tests (basic range checks)
local rand = math.random()
assert_equal(rand >= 0 and rand < 1, true)
local rand_n = math.random(10)
assert_equal(rand_n >= 1 and rand_n <= 10, true)
local rand_range = math.random(5, 10)
assert_equal(rand_range >= 5 and rand_range <= 10, true)
assertEqual(rand >= 0 and rand < 1, true)
local randN = math.random(10)
assertEqual(randN >= 1 and randN <= 10, true)
local randRange = math.random(5, 10)
assertEqual(randRange >= 5 and randRange <= 10, true)
-- Unsigned less than comparison
assert_equal(math.ult(1, 2), true)
assert_equal(math.ult(2, 1), false)
assertEqual(math.ult(1, 2), true)
assertEqual(math.ult(2, 1), false)

View File

@ -100,7 +100,7 @@ export async function interpolateLuaString(
return result;
}
export const spaceLuaApi = new LuaTable({
export const spaceluaApi = new LuaTable({
/**
* 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.
* @returns The parsed expression.
*/
parse_expression: new LuaBuiltinFunction(
parseExpression: new LuaBuiltinFunction(
(_sf, luaExpression: string) => {
return parseExpressionString(luaExpression);
},
@ -121,7 +121,7 @@ export const spaceLuaApi = new LuaTable({
* @param envAugmentation - An optional environment to augment the global environment with.
* @returns The result of the evaluated expression.
*/
eval_expression: new LuaBuiltinFunction(
evalExpression: new LuaBuiltinFunction(
async (sf, parsedExpr: LuaExpression, envAugmentation?: LuaTable) => {
const env = createAugmentedEnv(sf, envAugmentation);
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
*/
base_url: new LuaBuiltinFunction(
baseUrl: new LuaBuiltinFunction(
() => {
// Deal with Deno
if (typeof location === "undefined") {

View File

@ -1,15 +1,8 @@
local function assert_equal(a, b)
if a ~= b then
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)
local parsedExpr = spacelua.parseExpression("1 + 1")
local evalResult = spacelua.evalExpression(parsedExpr)
assert(evalResult == 2, "Eval should return 2")
-- Slightly more advanced example with augmented environment
local parsedExpr = space_lua.parse_expression("tostring(a + 1)")
local evalResult = space_lua.eval_expression(parsedExpr, { a = 1 })
assert(evalResult == "2", "Eval should return 2 as a string")
local parsedExpr = spacelua.parseExpression("tostring(a + 1)")
local evalResult = spacelua.evalExpression(parsedExpr, { a = 1 })
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) => {
return s.trim();
}),
trim_start: new LuaBuiltinFunction((_sf, s: string) => {
trimStart: new LuaBuiltinFunction((_sf, s: string) => {
return s.trimStart();
}),
trim_end: new LuaBuiltinFunction((_sf, s: string) => {
trimEnd: new LuaBuiltinFunction((_sf, s: string) => {
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 result = s.match(regex);
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");
return () => {
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
error("Assertion failed: " .. a .. " is not equal to " .. b)
end
@ -73,80 +73,80 @@ assert(result == "hello-world", "Magic character replacement failed")
-- Test string.match
local m1, m2 = string.match("hello world", "(h)(ello)")
assert_equal(m1, "h")
assert_equal(m2, "ello")
assertEqual(m1, "h")
assertEqual(m2, "ello")
-- Test match with init position - need to capture the group
local init_match = string.match("hello world", "(world)", 7)
assert_equal(init_match, "world")
local initMatch = string.match("hello world", "(world)", 7)
assertEqual(initMatch, "world")
-- Test string.gmatch
local words = {}
for word in string.gmatch("hello world lua", "%w+") do
table.insert(words, word)
end
assert_equal(words[1], "hello")
assert_equal(words[2], "world")
assert_equal(words[3], "lua")
assertEqual(words[1], "hello")
assertEqual(words[2], "world")
assertEqual(words[3], "lua")
-- Test string.reverse
assert_equal(string.reverse("hello"), "olleh")
assert_equal(string.reverse(""), "")
assertEqual(string.reverse("hello"), "olleh")
assertEqual(string.reverse(""), "")
-- Test string.split
local parts = string.split("a,b,c", ",")
assert_equal(parts[1], "a")
assert_equal(parts[2], "b")
assert_equal(parts[3], "c")
assertEqual(parts[1], "a")
assertEqual(parts[2], "b")
assertEqual(parts[3], "c")
-- Test non-standard string extensions
assert_equal(string.startswith("hello world", "hello"), true)
assert_equal(string.startswith("hello world", "world"), false)
assertEqual(string.startswith("hello world", "hello"), true)
assertEqual(string.startswith("hello world", "world"), false)
assert_equal(string.endswith("hello world", "world"), true)
assert_equal(string.endswith("hello world", "hello"), false)
assertEqual(string.endswith("hello world", "world"), true)
assertEqual(string.endswith("hello world", "hello"), false)
-- Extended string.match tests
-- Basic pattern matching
assert_equal(string.match("hello", "h"), "h")
assert_equal(string.match("hello", "hello"), "hello")
assertEqual(string.match("hello", "h"), "h")
assertEqual(string.match("hello", "hello"), "hello")
-- Test with no matches
assert_equal(string.match("hello", "x"), nil)
assertEqual(string.match("hello", "x"), nil)
-- Test with captures
local m1, m2 = string.match("hello", "(h)(ello)")
assert_equal(m1, "h")
assert_equal(m2, "ello")
assertEqual(m1, "h")
assertEqual(m2, "ello")
-- Test with init position
local init_match = string.match("hello world", "(world)", 7)
assert_equal(init_match, "world")
local initMatch = string.match("hello world", "(world)", 7)
assertEqual(initMatch, "world")
-- 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
assert_equal(string.match("123", "%d+"), "123")
assert_equal(string.match("abc123", "%a+"), "abc")
assert_equal(string.match(" abc", "%s+"), " ")
assertEqual(string.match("123", "%d+"), "123")
assertEqual(string.match("abc123", "%a+"), "abc")
assertEqual(string.match(" abc", "%s+"), " ")
-- Test multiple captures
local day, month, year = string.match("2024-03-14", "(%d+)-(%d+)-(%d+)")
assert_equal(day, "2024")
assert_equal(month, "03")
assert_equal(year, "14")
assertEqual(day, "2024")
assertEqual(month, "03")
assertEqual(year, "14")
-- Test optional captures
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 = {}
for match in string.match_regex_all("hellolllbl", "(l+)") do
for match in string.matchRegexAll("hellolllbl", "(l+)") do
table.insert(matches, match)
end
assert_equal(#matches, 3)
assert_equal(matches[1][1], "ll")
assert_equal(matches[2][1], "lll")
assert_equal(matches[3][1], "l")
assertEqual(#matches, 3)
assertEqual(matches[1][1], "ll")
assertEqual(matches[2][1], "lll")
assertEqual(matches[3][1], "l")

View File

@ -34,9 +34,7 @@ function exposeSyscalls(env: LuaEnv, system: System<any>) {
const luaFn = new LuaNativeJSFunction((...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(snakeCase(fn), luaFn, nativeFs);
}
}
@ -66,9 +64,9 @@ export async function handleLuaError(e: LuaRuntimeError, system: System<any>) {
console.error(
"Lua eval exception",
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
const pageRef = parsePageRef(e.sf.astCtx.ref);
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,
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,
[
"idx",

View File

@ -6,11 +6,11 @@ Config library for defining and getting config values
-- priority: 10
config = {}
local config_values = {}
local config_schema = {}
local configValues = {}
local configSchema = {}
function config.define(key, schema)
config_schema[key] = schema or true
configSchema[key] = schema or true
end
function config.set(keyOrTable, value)
@ -21,20 +21,19 @@ function config.set(keyOrTable, value)
return
end
local key = keyOrTable
local schema = config_schema[key]
local schema = configSchema[key]
if schema == nil then
error("Config key not defined: " .. key)
end
if schema != true then
local result = jsonschema.validate_object(schema, value)
local result = jsonschema.validateObject(schema, value)
if result != nil then
error("Validation error (" .. key .. "): " .. result)
end
end
config_values[key] = value
configValues[key] = value
end
function config.get(key)
return config_values[key]
return configValues[key]
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"}
-- Are we in a comment?
local function in_comment(line)
local function inComment(line)
return string.find(line, "--")
end
-- Are we in a string?
local function in_string(line)
local single_quotes = 0
local double_quotes = 0
local function inString(line)
local singleQuotes = 0
local doubleQuotes = 0
local brackets = 0
for i = 1, string.len(line) do
local c = line[i]
if c == "'" then
single_quotes = single_quotes + 1
singleQuotes = singleQuotes + 1
elseif c == '"' then
double_quotes = double_quotes + 1
doubleQuotes = doubleQuotes + 1
elseif c == "[" and line[i+1] == "[" then
brackets = brackets + 1
elseif c == "]" and line[i-1] == "]" then
brackets = brackets - 1
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
-- API code completion for Lua
@ -37,33 +37,33 @@ event.listen {
name = "editor:complete",
run = function(e)
local parents = e.data.parentNodes
local found_space_lua = false
local foundSpaceLua = false
for _, parent in ipairs(parents) do
if string.startswith(parent, "FencedCode:space-lua") then
found_space_lua = true
foundSpaceLua = true
end
end
if not found_space_lua then
if not foundSpaceLua then
return
end
local line_prefix = e.data.linePrefix
if in_comment(line_prefix) or in_string(line_prefix) then
local linePrefix = e.data.linePrefix
if inComment(linePrefix) or inString(linePrefix) then
return
end
local pos = e.data.pos
local propaccess_prefix = string.match_regex(line_prefix, "([a-zA-Z_0-9]+\\.)*([a-zA-Z_0-9]*)$")
if not propaccess_prefix or not propaccess_prefix[1] then
local propaccessPrefix = string.matchRegex(linePrefix, "([a-zA-Z_0-9]+\\.)*([a-zA-Z_0-9]*)$")
if not propaccessPrefix or not propaccessPrefix[1] then
-- No propaccess prefix, so we can't complete
return
end
-- Split propaccess and traverse
local prop_parts = string.split(propaccess_prefix[1], ".")
local current_value = _CTX._GLOBAL
local propParts = string.split(propaccessPrefix[1], ".")
local currentValue = _CTX._GLOBAL
local failed = false
for i = 1, #prop_parts-1 do
local prop = prop_parts[i]
if current_value then
current_value = current_value[prop]
for i = 1, #propParts-1 do
local prop = propParts[i]
if currentValue then
currentValue = currentValue[prop]
else
failed = true
end
@ -71,13 +71,13 @@ event.listen {
if failed then
return
end
local last_prop = prop_parts[#prop_parts]
if table.includes(LUA_KEYWORDS, last_prop) then
local lastProp = propParts[#propParts]
if table.includes(LUA_KEYWORDS, lastProp) then
return
end
local options = {}
for key, val in pairs(current_value) do
if string.startswith(key, last_prop) and val then
for key, val in pairs(currentValue) do
if string.startswith(key, lastProp) and val then
if val.call then
-- We got a function
if val.body then
@ -106,7 +106,7 @@ event.listen {
end
if #options > 0 then
return {
from = pos - string.len(last_prop),
from = pos - string.len(lastProp),
options = options
}
end
@ -118,34 +118,33 @@ event.listen {
Various useful slash templates.
```space-lua
template.define_slash_command {
template.defineSlashCommand {
name = "function",
description = "Lua function",
only_contexts = {"FencedCode:space-lua"},
onlyContexts = {"FencedCode:space-lua"},
template = template.new [==[function |^|()
end]==]
}
template.define_slash_command {
template.defineSlashCommand {
name = "tpl",
description = "Lua template",
only_contexts = {"FencedCode:space-lua"},
onlyContexts = {"FencedCode:space-lua"},
template = template.new "template.new[==[|^|]==]"
}
template.define_slash_command {
template.defineSlashCommand {
name = "lua-query",
description = "Lua query",
only_contexts = {"FencedCode:space-lua", "LuaDirective"},
onlyContexts = {"FencedCode:space-lua", "LuaDirective"},
template = template.new 'query[[from index.tag "|^|"]]'
}
-- A query embedded in ${}
template.define_slash_command {
template.defineSlashCommand {
name = "query",
description = "Lua query",
except_contexts = {"FencedCode:space-lua", "LuaDirective"},
exceptContexts = {"FencedCode:space-lua", "LuaDirective"},
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
-- Renders a page object as a linked list item
templates.page_item = template.new([==[
templates.pageItem = template.new([==[
* [[${name}]]
]==])
-- Renders a task object as a togglable task
templates.task_item = template.new([==[
templates.taskItem = template.new([==[
* [${state}] [[${ref}]] ${name}
]==])
```
# Examples
`template.page_item`:
${template.each(query[[from index.tag "page" limit 3]], templates.page_item)}
`template.pageItem`:
${template.each(query[[from index.tag "page" limit 3]], templates.pageItem)}
`template.task_item`:
`template.taskItem`:
* [ ] Task 1
* [ ] 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
-- Creates a new template function from a string template
function template.new(template_str)
function template.new(templateStr)
-- Preprocess: strip indentation
local lines = {}
local split_lines = string.split(template_str, "\n")
for _, line in ipairs(split_lines) do
local splitLines = string.split(templateStr, "\n")
for _, line in ipairs(splitLines) do
line = string.gsub(line, "^ ", "")
table.insert(lines, line)
end
template_str = table.concat(lines, "\n")
templateStr = table.concat(lines, "\n")
return function(obj)
return space_lua.interpolate(template_str, obj)
return spacelua.interpolate(templateStr, obj)
end
end
-- Creates a template-based slash command, keys for def are:
-- name: name 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
-- except_contexts: parent AST nodes in which this slash command is not available
-- onlyContexts: parent AST nodes in which this slash command is available, defaults to everywhere
-- exceptContexts: parent AST nodes in which this slash command is not available
-- 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_regex: match regex to apply the template to
function template.define_slash_command(def)
slash_command.define {
-- matchRegex: match regex to apply the template to
function template.defineSlashCommand(def)
slashcommand.define {
name = def.name,
description = def.description,
onlyContexts = def.only_contexts,
exceptContexts = def.except_contexts,
onlyContexts = def.onlyContexts,
exceptContexts = def.exceptContexts,
run = function()
system.invoke_function("template.applySnippetTemplate", def.template(), {
insertAt = def.insert_at,
system.invokeFunction("template.applySnippetTemplate", def.template(), {
insertAt = def.insertAt,
match = def.match,
matchRegex = def.match_regex
matchRegex = def.matchRegex
})
end
}
end
```

View File

@ -81,7 +81,7 @@ export function luaDirectivePlugin(client: Client) {
return result;
} catch (e: any) {
if (e instanceof LuaRuntimeError) {
if (e.sf.astCtx) {
if (e.sf?.astCtx) {
const source = resolveASTReference(e.sf.astCtx);
if (source) {
// 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;
div.innerHTML = html;
if ((widgetContent as any)?.display === "block") {
div.className = "sb-lua-directive-block";
div.className += " sb-lua-directive-block";
} else {
div.className = "sb-lua-directive-inline";
div.className += " sb-lua-directive-inline";
}
attachWidgetEventHandlers(div, this.client, this.from);
this.client.setWidgetCache(

View File

@ -2,4 +2,4 @@ This describes the APIs available in [[Space Lua]]
${template.each(query[[
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.
## clientStore.set(key, value)
Sets a value in the client store.

View File

@ -1,14 +1,24 @@
APIs related to editor commands
### command.define(command_def)
### command.define(commandDef)
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:
```lua
command.define {
name = "My custom command",
run = function()
editor.flash_notification "Triggered my custom command"
editor.flashNotification "Triggered my custom command"
end
}
```

View File

@ -1,4 +1,3 @@
The Datastore API provides functions for interacting with a key-value store that has query capabilities.
# Key-Value Operations
@ -30,7 +29,7 @@ datastore.del("user:123")
# Batch Operations
## datastore.batch_set(kvs)
## datastore.batchSet(kvs)
Sets multiple key-value pairs in a single operation.
Example:
@ -39,27 +38,25 @@ local kvs = {
{key = "user:1", value = {name = "Alice"}},
{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.
Example:
```lua
local keys = {"user:1", "user:2"}
local values = datastore.batch_get(keys)
local values = datastore.batchGet(keys)
for _, value in ipairs(values) do
print(value.name)
end
```
## datastore.batch_del(keys)
## datastore.batchDel(keys)
Deletes multiple values in a single operation.
Example:
```lua
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.
## debug.reset_client()
## debug.resetClient()
Completely wipes the client state, including cached files and databases.
Example:
```lua
debug.reset_client()
debug.resetClient()
print("Client state has been reset")
```
@ -18,4 +18,3 @@ Example:
```lua
debug.cleanup()
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.get_current_page()
### editor.getCurrentPage()
Returns the name of the page currently open in the editor.
Example:
```lua
local page = editor.get_current_page()
print("Current page: " .. page)
```
Example: ${editor.getCurrentPage()}
### editor.get_current_page_meta()
### editor.getCurrentPageMeta()
Returns the meta data of the page currently open in the editor.
Example:
```lua
local meta = editor.get_current_page_meta()
print("Last modified: " .. meta.last_modified)
```
${editor.getCurrentPageMeta()}
## Text Operations
### editor.get_text()
### editor.getText()
Returns the full text of the currently open page.
Example:
```lua
local text = editor.get_text()
local text = editor.getText()
print("Document length: " .. #text)
```
### editor.set_text(text, isolate_history)
### editor.setText(text, isolateHistory)
Updates the editor text while preserving cursor location.
Example:
```lua
local text = editor.get_text()
editor.set_text(text:upper(), false) -- Convert to uppercase
local text = editor.getText()
editor.setText(text:upper(), false) -- Convert to uppercase
```
### editor.insert_at_pos(text, pos)
### editor.insertAtPos(text, pos)
Insert text at the specified position.
Example:
```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.
Example:
```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.
Example:
```lua
editor.insert_at_cursor("Inserted at cursor")
editor.insertAtCursor("Inserted at cursor")
```
## Cursor Control
### editor.get_cursor()
### editor.getCursor()
Returns the cursor position as character offset.
Example:
```lua
local pos = editor.get_cursor()
local pos = editor.getCursor()
print("Cursor at position: " .. pos)
```
### editor.get_selection()
### editor.getSelection()
Returns the current selection range.
Example:
```lua
local sel = editor.get_selection()
local sel = editor.getSelection()
print("Selection from " .. sel.from .. " to " .. sel.to)
```
### editor.set_selection(from, to)
### editor.setSelection(from, to)
Sets the current selection range.
Example:
```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.
Example:
```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.
Example:
```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.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)
### editor.openPageNavigator(mode)
Opens the page navigator.
Example:
```lua
editor.open_page_navigator("page")
editor.openPageNavigator("page")
```
### editor.open_command_palette()
### editor.openCommandPalette()
Opens the command palette.
Example:
```lua
editor.open_command_palette()
editor.openCommandPalette()
```
## UI Operations
### editor.show_panel(id, mode, html, script)
### editor.showPanel(id, mode, html, script)
Shows a panel in the editor.
Example:
```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.
Example:
```lua
editor.hide_panel("rhs")
editor.hidePanel("rhs")
```
### editor.flash_notification(message, type)
### editor.flashNotification(message, type)
Shows a flash notification.
Example:
```lua
editor.flash_notification("Operation completed", "info")
editor.flashNotification("Operation completed", "info")
```
### editor.prompt(message, default_value)
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)
### editor.downloadFile(filename, dataUrl)
Triggers a file download in the browser.
Example:
```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.
Example:
```lua
local file = editor.upload_file(".txt", nil)
local file = editor.uploadFile(".txt", nil)
print("Uploaded: " .. file.name)
```
## Clipboard Operations
### editor.copy_to_clipboard(data)
### editor.copyToClipboard(data)
Copies data to the clipboard.
Example:
```lua
editor.copy_to_clipboard("Copied text")
editor.copyToClipboard("Copied text")
```
## Code Folding
### 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()
### editor.toggleFold()
Toggles code folding at the current position.
Example:
```lua
editor.toggle_fold()
editor.toggleFold()
```
### editor.fold_all()
### editor.foldAll()
Folds all foldable regions.
Example:
```lua
editor.fold_all()
editor.foldAll()
```
### editor.unfold_all()
### editor.unfoldAll()
Unfolds all folded regions.
Example:
```lua
editor.unfold_all()
editor.unfoldAll()
```
## History Operations
### 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()
### editor.openSearchPanel()
Opens the editor's search panel.
Example:
```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.listen(listener_def)
### event.listen(listenerDef)
Register an event listener.
```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.
Example:
@ -25,19 +25,18 @@ Example:
event.dispatch("custom.event", {message = "Hello"})
-- 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
print(response)
end
```
### event.list_events()
### event.listEvents()
Lists all events currently registered (listened to) on the SilverBullet event bus.
Example:
```lua
local events = event.list_events()
for _, event_name in ipairs(events) do
print("Registered event: " .. event_name)
local events = event.listEvents()
for _, eventName in ipairs(events) do
print("Registered event: " .. eventName)
end
```

View File

@ -9,7 +9,7 @@ Example:
${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.
Example:
@ -18,24 +18,23 @@ local objects = {
{tag = "mytask", ref="task1", content = "Buy groceries"},
{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.
Example:
```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.
Example:
```lua
local task = index.get_object_by_ref("my page", "mytask", "task1")
local task = index.getObjectByRef("my page", "mytask", "task1")
if task then
print("Found task: " .. task.content)
end
```

View File

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

View File

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

View File

@ -2,7 +2,7 @@ The Language API provides functions for parsing code in various programming lang
## Language Operations
### language.parse_language(language, code)
### language.parseLanguage(language, code)
Parses a piece of code using any of the supported SilverBullet languages.
Example:
@ -13,7 +13,7 @@ function hello() {
}
]]
local tree = language.parse_language("javascript", [[
local tree = language.parseLanguage("javascript", [[
function hello() {
console.log("Hello, world!");
}
@ -21,8 +21,8 @@ function hello() {
print("Parsed syntax tree:", tree)
```
### language.list_languages()
### language.listLanguages()
Lists all supported languages in fenced code blocks.
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.parse_markdown(text)
### markdown.parseMarkdown(text)
Parses a piece of markdown text into a ParseTree.
Example:
@ -15,18 +15,17 @@ local text = [[
This is a **bold** statement.
]]
local tree = markdown.parse_markdown(text)
local tree = markdown.parseMarkdown(text)
print("Parsed markdown tree:", tree)
```
### markdown.render_parse_tree(tree)
### markdown.renderParseTree(tree)
Renders a ParseTree back to markdown text.
Example:
```lua
local text = "# Title\n\nSome text"
local tree = markdown.parse_markdown(text)
local tree = markdown.parseMarkdown(text)
-- Modify tree if needed
local rendered = markdown.render_parse_tree(tree)
local rendered = markdown.renderParseTree(tree)
print("Rendered markdown:", rendered)
```

View File

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

View File

@ -2,149 +2,148 @@ The Space API provides functions for interacting with pages, attachments, and fi
# Page Operations
## space.list_pages()
## space.listPages()
Returns a list of all pages in the space.
Example:
```lua
local pages = space.list_pages()
local pages = space.listPages()
for page in each(pages) do
print(page.name)
end
```
## space.read_page(name)
## space.readPage(name)
Reads the content of a page.
Example:
```lua
local content = space.read_page("welcome")
local content = space.readPage("welcome")
print(content) -- prints the content of the "welcome" page
```
## space.get_page_meta(name)
## space.getPageMeta(name)
Gets metadata for a specific page.
Example:
```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
```
## space.write_page(name, text)
## space.writePage(name, text)
Writes content to a page.
Example:
```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)
```
## space.delete_page(name)
## space.deletePage(name)
Deletes a page from the space.
Example:
```lua
space.delete_page("old-notes")
space.deletePage("old-notes")
```
# Attachment Operations
## space.list_attachments()
## space.listAttachments()
Returns a list of all attachments in the space.
Example:
```lua
local attachments = space.list_attachments()
local attachments = space.listAttachments()
for att in each(attachments) do
print(att.name, att.size)
end
```
## space.read_attachment(name)
## space.readAttachment(name)
Reads the content of an attachment.
Example:
```lua
local data = space.read_attachment("image.png")
local data = space.readAttachment("image.png")
print("Attachment size: " .. #data .. " bytes")
```
## space.write_attachment(name, data)
## space.writeAttachment(name, data)
Writes binary data to an attachment.
Example:
```lua
local binary_data = string.char(72, 69, 76, 76, 79) -- "HELLO" in binary
local meta = space.write_attachment("test.bin", binary_data)
local binaryData = string.char(72, 69, 76, 76, 79) -- "HELLO" in binary
local meta = space.writeAttachment("test.bin", binaryData)
print("Attachment saved with size: " .. meta.size)
```
## space.delete_attachment(name)
## space.deleteAttachment(name)
Deletes an attachment from the space.
Example:
```lua
space.delete_attachment("old-image.png")
space.deleteAttachment("old-image.png")
```
# File Operations
## space.list_files()
## space.listFiles()
Returns a list of all files in the space.
Example:
```lua
local files = space.list_files()
local files = space.listFiles()
for _, file in ipairs(files) do
print(file.name, file.size)
end
```
## space.get_file_meta(name)
## space.getFileMeta(name)
Gets metadata for a specific file.
Example:
```lua
local meta = space.get_file_meta("document.txt")
local meta = space.getFileMeta("document.txt")
print(meta.name, meta.modified, meta.size)
```
## space.read_file(name)
## space.readFile(name)
Reads the content of a file.
Example:
```lua
local content = space.read_file("document.txt")
local content = space.readFile("document.txt")
print("File size: " .. #content .. " bytes")
```
## space.write_file(name, data)
## space.writeFile(name, data)
Writes binary data to a file.
Example:
```lua
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)
```
## space.delete_file(name)
## space.deleteFile(name)
Deletes a file from the space.
Example:
```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.
Example:
```lua
if space.file_exists("config.json") then
if space.fileExists("config.json") then
print("Config file exists!")
else
print("Config file not found")
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.is_syncing()
### sync.isSyncing()
Checks if a sync is currently in progress.
Example:
```lua
if sync.is_syncing() then
if sync.isSyncing() then
print("Sync in progress...")
end
```
### sync.has_initial_sync_completed()
### sync.hasInitialSyncCompleted()
Checks if an initial sync has completed.
Example:
```lua
if sync.has_initial_sync_completed() then
if sync.hasInitialSyncCompleted() then
print("Initial sync completed")
else
print("Waiting for initial sync...")
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.
Example:
```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.
Example:
```lua
local changes = sync.schedule_space_sync()
local changes = sync.scheduleSpaceSync()
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
### system.invoke_function(name, ...)
### system.invokeFunction(name, ...)
Invokes a plug function by name.
Example:
```lua
-- 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.
Example:
```lua
system.invoke_command("editor.save", {})
system.invokeCommand("editor.save", {})
```
### system.invoke_space_function(name, ...)
### system.invokeSpaceFunction(name, ...)
Invokes a space function by name.
Example:
```lua
local result = system.invoke_space_function("custom_function", "arg1", "arg2")
local result = system.invokeSpaceFunction("customFunction", "arg1", "arg2")
print("Function result:", result)
```
## System Information
### system.list_commands()
### system.listCommands()
Lists all available commands.
Example:
```lua
local commands = system.list_commands()
local commands = system.listCommands()
for name, def in pairs(commands) do
print(name .. ": " .. def.description)
end
```
### system.list_syscalls()
### system.listSyscalls()
Lists all available syscalls.
Example:
```lua
local syscalls = system.list_syscalls()
local syscalls = system.listSyscalls()
for _, syscall in ipairs(syscalls) do
print(syscall.name)
end
```
### system.get_env()
### system.getEnv()
Returns the runtime environment ("server", "client", or undefined for hybrid).
Example:
```lua
local env = system.get_env()
local env = system.getEnv()
print("Running in environment: " .. (env or "hybrid"))
```
### system.get_mode()
### system.getMode()
Returns the current mode of the system ("ro" or "rw").
Example:
```lua
local mode = system.get_mode()
local mode = system.getMode()
print("System mode: " .. mode)
```
### system.get_version()
### system.getVersion()
Returns the SilverBullet version.
Example:
```lua
local version = system.get_version()
local version = system.getVersion()
print("SilverBullet version: " .. version)
```
## Configuration
### system.get_space_config(key, default_value)
### system.getSpaceConfig(key, defaultValue)
Loads space configuration values.
Example:
```lua
-- Get specific config value
local value = system.get_space_config("theme", "light")
local value = system.getSpaceConfig("theme", "light")
-- Get all config values
local config = system.get_space_config()
local config = system.getSpaceConfig()
for key, value in pairs(config) do
print(key .. ": " .. value)
end
```
### system.reload_config()
### system.reloadConfig()
Triggers an explicit reload of the configuration.
Example:
```lua
local new_config = system.reload_config()
system.reloadConfig()
print("Configuration reloaded")
```
### system.reload_plugs()
### system.reloadPlugs()
Triggers a reload of all plugs.
Example:
```lua
system.reload_plugs()
system.reloadPlugs()
print("All plugs reloaded")
```

View File

@ -8,10 +8,10 @@ Example:
```space-lua
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)
Iterates over a collection and renders a template for each item.
@ -20,4 +20,4 @@ Example:
${template.each(query[[from index.tag "page" limit 3]], template.new[==[
* ${name}
]==])}
]==])}

View File

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

View File

@ -5,6 +5,7 @@ An attempt at documenting the changes/new features introduced in each release.
## 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._
* **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))
## 0.10.1
@ -405,4 +406,3 @@ Other notable changes:
* [BLOCKED] A task thats blocked
[[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.

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.
* (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.
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!
## Commands
Custom commands can be defined using `command.define`:
Custom commands can be defined using [[API/command#command.define(commandDef)]]:
```space-lua
command.define {
name = "Hello World",
run = function()
editor.flash_notification "Hello world!"
editor.flashNotification "Hello world!"
event.dispatch("my-custom-event", {name="Pete"})
end
}
@ -116,13 +116,13 @@ command.define {
Try it: {[Hello World]}
## Event listeners
You can listen to events using `event.listen`:
You can listen to events using [[API/event#event.listen(listenerDef)]]:
```space-lua
event.listen {
name = "my-custom-event";
run = function(e)
editor.flash_notification("Custom triggered: "
editor.flashNotification("Custom triggered: "
.. e.data.name)
end
}
@ -135,64 +135,20 @@ Space Lua currently introduces a few new features on top core Lua:
2. 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._GLOBAL` providing access to the global scope
# API
Lua APIs, which should be (roughly) implemented according to the Lua standard.
* `print`
* `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).
All of these are available via the global namespace:
${template.each(query[[from index.tag "page" where string.startswith(name, "API/")]], templates.pageItem)}
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
local function call_some_things()
local text = space.read_page(editor.get_current_page())
local function callSomeThings()
local text = space.readPage(editor.getCurrentPage())
print("Current page text", text)
end
```
@ -210,4 +166,4 @@ Space Lua is intended to be a more or less complete implementation of [Lua 5.4](
Lua is purpose-designed to be a simple, [easy to learn](https://www.lua.org/manual/5.4/), yet powerful language for extending existing applications. It is commonly used in the gaming industry, but to extend many other applications. If you know any other programming language, you will be able to learn Lua within hours or less.
## Why a custom Lua runtime?
Rather than using a WebAssembly or other implementation of Lua that could run in the browser and server, we have opted for a custom implementation. This is achievable because Lua is a relatively simple and small language to implement and allows for deep integration in the custom Lua runtime. The thing that triggered a custom implementation was the need to call asynchronous (JavaScipt) APIs from Lua, without having to resort to ugly asynchronous callback-style API design (Lua does not support async-await). In SilverBullets Lua implementation, the differences between asynchronous and synchronous APIs is fully abstracted away, which makes for a very clean development experience.
Rather than using a WebAssembly or other implementation of Lua that could run in the browser and server, we have opted for a custom implementation. This is achievable because Lua is a relatively simple and small language to implement and allows for deep integration in the custom Lua runtime. The thing that triggered a custom implementation was the need to call asynchronous (JavaScipt) APIs from Lua, without having to resort to ugly asynchronous callback-style API design (Lua does not support async-await). In SilverBullets Lua implementation, the differences between asynchronous and synchronous APIs is fully abstracted away, which makes for a very clean development experience.