Lua code complete
parent
f940a04dc8
commit
17ec0e41d1
|
@ -0,0 +1,149 @@
|
||||||
|
#meta
|
||||||
|
|
||||||
|
Editor support for Lua, implemented in Lua. Of course.
|
||||||
|
|
||||||
|
# Code complete support
|
||||||
|
```space-lua
|
||||||
|
local LUA_KEYWORDS = {"do", "if", "then", "for", "else", "end", "function", "local", "return"}
|
||||||
|
|
||||||
|
-- Are we in a comment?
|
||||||
|
local function in_comment(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 brackets = 0
|
||||||
|
for i = 1, string.len(line) do
|
||||||
|
local c = line[i]
|
||||||
|
if c == "'" then
|
||||||
|
single_quotes = single_quotes + 1
|
||||||
|
elseif c == '"' then
|
||||||
|
double_quotes = double_quotes + 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
|
||||||
|
end
|
||||||
|
|
||||||
|
-- API code completion for Lua
|
||||||
|
-- Completes something.somethingelse APIs
|
||||||
|
event.listen {
|
||||||
|
name = "editor:complete",
|
||||||
|
run = function(e)
|
||||||
|
local parents = e.data.parentNodes
|
||||||
|
local found_space_lua = false
|
||||||
|
for _, parent in ipairs(parents) do
|
||||||
|
if string.startswith(parent, "FencedCode:space-lua") then
|
||||||
|
found_space_lua = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not found_space_lua then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local line_prefix = e.data.linePrefix
|
||||||
|
if in_comment(line_prefix) or in_string(line_prefix) 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
|
||||||
|
-- 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 failed = false
|
||||||
|
for i = 1, #prop_parts-1 do
|
||||||
|
local prop = prop_parts[i]
|
||||||
|
if current_value then
|
||||||
|
current_value = current_value[prop]
|
||||||
|
else
|
||||||
|
failed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local last_prop = prop_parts[#prop_parts]
|
||||||
|
if table.includes(LUA_KEYWORDS, last_prop) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not failed then
|
||||||
|
local options = {}
|
||||||
|
for key, val in pairs(current_value) do
|
||||||
|
if string.startswith(key, last_prop) and val then
|
||||||
|
if val.call then
|
||||||
|
-- We got a function
|
||||||
|
if val.body then
|
||||||
|
-- Function defined in Lua
|
||||||
|
table.insert(options, {
|
||||||
|
label = key .. "(" .. table.concat(val.body.parameters, ", ") ..")",
|
||||||
|
apply = key .. "(",
|
||||||
|
detail = "Lua function"
|
||||||
|
})
|
||||||
|
else
|
||||||
|
-- Builtin
|
||||||
|
table.insert(options, {
|
||||||
|
label = key .. "()",
|
||||||
|
apply = key .. "(",
|
||||||
|
detail = "Lua built-in"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- Table
|
||||||
|
table.insert(options, {
|
||||||
|
label = key,
|
||||||
|
detail = "Lua table"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if #options > 0 then
|
||||||
|
return {
|
||||||
|
from = pos - string.len(last_prop),
|
||||||
|
options = options
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Slash templates
|
||||||
|
Various useful slash templates.
|
||||||
|
|
||||||
|
```space-lua
|
||||||
|
template.define_slash_command {
|
||||||
|
name = "function",
|
||||||
|
description = "Lua function",
|
||||||
|
only_contexts = {"FencedCode:space-lua"},
|
||||||
|
template = template.new [==[function |^|()
|
||||||
|
end]==]
|
||||||
|
}
|
||||||
|
|
||||||
|
template.define_slash_command {
|
||||||
|
name = "tpl",
|
||||||
|
description = "Lua template",
|
||||||
|
only_contexts = {"FencedCode:space-lua"},
|
||||||
|
template = template.new "template.new[==[|^|]==]"
|
||||||
|
}
|
||||||
|
|
||||||
|
template.define_slash_command {
|
||||||
|
name = "lua-query",
|
||||||
|
description = "Lua query",
|
||||||
|
only_contexts = {"FencedCode:space-lua", "LuaDirective"},
|
||||||
|
template = template.new 'query[[from index.tag "|^|"]]'
|
||||||
|
}
|
||||||
|
|
||||||
|
-- A query embedded in ${}
|
||||||
|
template.define_slash_command {
|
||||||
|
name = "query",
|
||||||
|
description = "Lua query",
|
||||||
|
except_contexts = {"FencedCode:space-lua", "LuaDirective"},
|
||||||
|
template = function() return '${query[[from index.tag "|^|"]]}' end
|
||||||
|
}
|
||||||
|
```
|
|
@ -24,7 +24,7 @@ export type AppSlashCommand = {
|
||||||
const slashCommandRegexp = /([^\w:]|^)\/[\w#\-]*/;
|
const slashCommandRegexp = /([^\w:]|^)\/[\w#\-]*/;
|
||||||
|
|
||||||
export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
||||||
slashCommands = new Map<string, AppSlashCommand>();
|
slashCommands: AppSlashCommand[] = [];
|
||||||
private editor: Client;
|
private editor: Client;
|
||||||
|
|
||||||
constructor(editor: Client, private commonSystem: CommonSystem) {
|
constructor(editor: Client, private commonSystem: CommonSystem) {
|
||||||
|
@ -38,7 +38,7 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
||||||
buildAllCommands() {
|
buildAllCommands() {
|
||||||
const system = this.commonSystem.system;
|
const system = this.commonSystem.system;
|
||||||
|
|
||||||
this.slashCommands.clear();
|
this.slashCommands = [];
|
||||||
for (const plug of system.loadedPlugs.values()) {
|
for (const plug of system.loadedPlugs.values()) {
|
||||||
for (
|
for (
|
||||||
const [name, functionDef] of Object.entries(
|
const [name, functionDef] of Object.entries(
|
||||||
|
@ -49,7 +49,7 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const cmd = functionDef.slashCommand;
|
const cmd = functionDef.slashCommand;
|
||||||
this.slashCommands.set(cmd.name, {
|
this.slashCommands.push({
|
||||||
slashCommand: cmd,
|
slashCommand: cmd,
|
||||||
run: () => {
|
run: () => {
|
||||||
return plug.invoke(name, [cmd]);
|
return plug.invoke(name, [cmd]);
|
||||||
|
@ -59,11 +59,11 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
||||||
}
|
}
|
||||||
// Iterate over script defined slash commands
|
// Iterate over script defined slash commands
|
||||||
for (
|
for (
|
||||||
const [name, command] of Object.entries(
|
const command of Object.values(
|
||||||
this.commonSystem.scriptEnv.slashCommands,
|
this.commonSystem.scriptEnv.slashCommands,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this.slashCommands.set(name, command);
|
this.slashCommands.push(command);
|
||||||
}
|
}
|
||||||
// Iterate over all shortcuts
|
// Iterate over all shortcuts
|
||||||
if (this.editor.config?.shortcuts) {
|
if (this.editor.config?.shortcuts) {
|
||||||
|
@ -71,7 +71,7 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
||||||
for (const shortcut of this.editor.config.shortcuts) {
|
for (const shortcut of this.editor.config.shortcuts) {
|
||||||
if (shortcut.slashCommand) {
|
if (shortcut.slashCommand) {
|
||||||
const parsedCommand = parseCommand(shortcut.command);
|
const parsedCommand = parseCommand(shortcut.command);
|
||||||
this.slashCommands.set(shortcut.slashCommand, {
|
this.slashCommands.push({
|
||||||
slashCommand: {
|
slashCommand: {
|
||||||
name: shortcut.slashCommand,
|
name: shortcut.slashCommand,
|
||||||
description: parsedCommand.alias || parsedCommand.name,
|
description: parsedCommand.alias || parsedCommand.name,
|
||||||
|
@ -110,7 +110,7 @@ export class SlashCommandHook implements Hook<SlashCommandHookT> {
|
||||||
|
|
||||||
// Check if the slash command is available in the current context
|
// Check if the slash command is available in the current context
|
||||||
const parentNodes = this.editor.extractParentNodes(ctx.state, currentNode);
|
const parentNodes = this.editor.extractParentNodes(ctx.state, currentNode);
|
||||||
for (const def of this.slashCommands.values()) {
|
for (const def of this.slashCommands) {
|
||||||
if (
|
if (
|
||||||
def.slashCommand.onlyContexts && !def.slashCommand.onlyContexts.some(
|
def.slashCommand.onlyContexts && !def.slashCommand.onlyContexts.some(
|
||||||
(context) => parentNodes.some((node) => node.startsWith(context)),
|
(context) => parentNodes.some((node) => node.startsWith(context)),
|
||||||
|
|
Loading…
Reference in New Issue