Update snippet generator script and handlers, bump snippet version to 5.4.1

This commit is contained in:
GreenXenith 2021-04-30 11:06:18 -07:00
parent aba32950fd
commit a3daba2cd2
8 changed files with 1581 additions and 1032 deletions

View File

@ -1,17 +1,7 @@
const vscode = require("vscode");
const fs = require("fs");
const path = require("path");
const snippets = require("./snippets.json");
// const extraSnippets = [
// {
// prefix: "minetest",
// body: "minetest",
// desc: "Global Minetest namespace.",
// kind: 4,
// detail: "(table)",
// }
// ];
const snippets = require("./smartsnippets.json");
const rootPath = vscode.workspace.workspaceFolders != undefined ? vscode.workspace.workspaceFolders[0].uri.fsPath : "";
const luacheckrc = `read_globals = {
@ -76,25 +66,26 @@ function activate(context) {
fs.existsSync(path.join(rootPath, "modpack.txt")))
) return [];
let token = document.getText(new vscode.Range(new vscode.Position(position.line, position.character - 1), position));
if (token == ".") {
token = document.getText(new vscode.Range(new vscode.Position(position.line, 0), position)).match(/(\w+)\.$/)[1];
}
let items = [];
const line = document.getText(new vscode.Range(new vscode.Position(position.line, 0), position));
const afterpos = new vscode.Range(position, new vscode.Position(position.line, position.character + 1));
const after = document.getText(afterpos);
for (const snippet of snippets) {
if (snippet.token == token && !(snippet.kind == 13 && !vscode.workspace.getConfiguration("editor").get("quickSuggestions").strings)) {
const item = new vscode.CompletionItem(snippet.prefix);
let items = [];
for (const snippet of snippets) {
if (snippet.token && line.match(new RegExp(`${snippet.token.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")}(?![^\\w\\n\\s\\r\\-])[\\w\\n\\s\\r\\-]*`))) {
let item = new vscode.CompletionItem(snippet.prefix);
item.insertText = new vscode.SnippetString(snippet.body);
item.documentation = new vscode.MarkdownString(snippet.desc);
item.kind = snippet.kind || null;
item.kind = snippet.kind;
item.detail = snippet.detail || snippet.prefix;
items.push(item);
}
}
item.additionalTextEdits = (snippet.token.match(/^[\(\[\{]$/) && after.match(/[\}\]\)]/)) ? [vscode.TextEdit.delete(afterpos)] : null;
return items;
items.push(item);
}
}
return items;
},
}, ":", ".", "[");

View File

@ -1,400 +0,0 @@
--
-- json.lua
--
-- Copyright (c) 2019 rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
--
local json = { _version = "0.1.2" }
-------------------------------------------------------------------------------
-- Encode
-------------------------------------------------------------------------------
local encode
local escape_char_map = {
[ "\\" ] = "\\\\",
[ "\"" ] = "\\\"",
[ "\b" ] = "\\b",
[ "\f" ] = "\\f",
[ "\n" ] = "\\n",
[ "\r" ] = "\\r",
[ "\t" ] = "\\t",
}
local escape_char_map_inv = { [ "\\/" ] = "/" }
for k, v in pairs(escape_char_map) do
escape_char_map_inv[v] = k
end
local function escape_char(c)
return escape_char_map[c] or string.format("\\u%04x", c:byte())
end
local function encode_nil(val)
return "null"
end
local function encode_table(val, stack)
local res = {}
stack = stack or {}
-- Circular reference?
if stack[val] then error("circular reference") end
stack[val] = true
if rawget(val, 1) ~= nil or next(val) == nil then
-- Treat as array -- check keys are valid and it is not sparse
local n = 0
for k in pairs(val) do
if type(k) ~= "number" then
error("invalid table: mixed or invalid key types")
end
n = n + 1
end
if n ~= #val then
error("invalid table: sparse array")
end
-- Encode
for i, v in ipairs(val) do
table.insert(res, encode(v, stack))
end
stack[val] = nil
return "[" .. table.concat(res, ",") .. "]"
else
-- Treat as an object
for k, v in pairs(val) do
if type(k) ~= "string" then
error("invalid table: mixed or invalid key types")
end
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
end
stack[val] = nil
return "{" .. table.concat(res, ",") .. "}"
end
end
local function encode_string(val)
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
end
local function encode_number(val)
-- Check for NaN, -inf and inf
if val ~= val or val <= -math.huge or val >= math.huge then
error("unexpected number value '" .. tostring(val) .. "'")
end
return string.format("%.14g", val)
end
local type_func_map = {
[ "nil" ] = encode_nil,
[ "table" ] = encode_table,
[ "string" ] = encode_string,
[ "number" ] = encode_number,
[ "boolean" ] = tostring,
}
encode = function(val, stack)
local t = type(val)
local f = type_func_map[t]
if f then
return f(val, stack)
end
error("unexpected type '" .. t .. "'")
end
function json.encode(val)
return ( encode(val) )
end
-------------------------------------------------------------------------------
-- Decode
-------------------------------------------------------------------------------
local parse
local function create_set(...)
local res = {}
for i = 1, select("#", ...) do
res[ select(i, ...) ] = true
end
return res
end
local space_chars = create_set(" ", "\t", "\r", "\n")
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
local literals = create_set("true", "false", "null")
local literal_map = {
[ "true" ] = true,
[ "false" ] = false,
[ "null" ] = nil,
}
local function next_char(str, idx, set, negate)
for i = idx, #str do
if set[str:sub(i, i)] ~= negate then
return i
end
end
return #str + 1
end
local function decode_error(str, idx, msg)
local line_count = 1
local col_count = 1
for i = 1, idx - 1 do
col_count = col_count + 1
if str:sub(i, i) == "\n" then
line_count = line_count + 1
col_count = 1
end
end
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
end
local function codepoint_to_utf8(n)
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
local f = math.floor
if n <= 0x7f then
return string.char(n)
elseif n <= 0x7ff then
return string.char(f(n / 64) + 192, n % 64 + 128)
elseif n <= 0xffff then
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
elseif n <= 0x10ffff then
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
f(n % 4096 / 64) + 128, n % 64 + 128)
end
error( string.format("invalid unicode codepoint '%x'", n) )
end
local function parse_unicode_escape(s)
local n1 = tonumber( s:sub(3, 6), 16 )
local n2 = tonumber( s:sub(9, 12), 16 )
-- Surrogate pair?
if n2 then
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
else
return codepoint_to_utf8(n1)
end
end
local function parse_string(str, i)
local has_unicode_escape = false
local has_surrogate_escape = false
local has_escape = false
local last
for j = i + 1, #str do
local x = str:byte(j)
if x < 32 then
decode_error(str, j, "control character in string")
end
if last == 92 then -- "\\" (escape char)
if x == 117 then -- "u" (unicode escape sequence)
local hex = str:sub(j + 1, j + 5)
if not hex:find("%x%x%x%x") then
decode_error(str, j, "invalid unicode escape in string")
end
if hex:find("^[dD][89aAbB]") then
has_surrogate_escape = true
else
has_unicode_escape = true
end
else
local c = string.char(x)
if not escape_chars[c] then
decode_error(str, j, "invalid escape char '" .. c .. "' in string")
end
has_escape = true
end
last = nil
elseif x == 34 then -- '"' (end of string)
local s = str:sub(i + 1, j - 1)
if has_surrogate_escape then
s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
end
if has_unicode_escape then
s = s:gsub("\\u....", parse_unicode_escape)
end
if has_escape then
s = s:gsub("\\.", escape_char_map_inv)
end
return s, j + 1
else
last = x
end
end
decode_error(str, i, "expected closing quote for string")
end
local function parse_number(str, i)
local x = next_char(str, i, delim_chars)
local s = str:sub(i, x - 1)
local n = tonumber(s)
if not n then
decode_error(str, i, "invalid number '" .. s .. "'")
end
return n, x
end
local function parse_literal(str, i)
local x = next_char(str, i, delim_chars)
local word = str:sub(i, x - 1)
if not literals[word] then
decode_error(str, i, "invalid literal '" .. word .. "'")
end
return literal_map[word], x
end
local function parse_array(str, i)
local res = {}
local n = 1
i = i + 1
while 1 do
local x
i = next_char(str, i, space_chars, true)
-- Empty / end of array?
if str:sub(i, i) == "]" then
i = i + 1
break
end
-- Read token
x, i = parse(str, i)
res[n] = x
n = n + 1
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "]" then break end
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
end
return res, i
end
local function parse_object(str, i)
local res = {}
i = i + 1
while 1 do
local key, val
i = next_char(str, i, space_chars, true)
-- Empty / end of object?
if str:sub(i, i) == "}" then
i = i + 1
break
end
-- Read key
if str:sub(i, i) ~= '"' then
decode_error(str, i, "expected string for key")
end
key, i = parse(str, i)
-- Read ':' delimiter
i = next_char(str, i, space_chars, true)
if str:sub(i, i) ~= ":" then
decode_error(str, i, "expected ':' after key")
end
i = next_char(str, i + 1, space_chars, true)
-- Read value
val, i = parse(str, i)
-- Set
res[key] = val
-- Next token
i = next_char(str, i, space_chars, true)
local chr = str:sub(i, i)
i = i + 1
if chr == "}" then break end
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
end
return res, i
end
local char_func_map = {
[ '"' ] = parse_string,
[ "0" ] = parse_number,
[ "1" ] = parse_number,
[ "2" ] = parse_number,
[ "3" ] = parse_number,
[ "4" ] = parse_number,
[ "5" ] = parse_number,
[ "6" ] = parse_number,
[ "7" ] = parse_number,
[ "8" ] = parse_number,
[ "9" ] = parse_number,
[ "-" ] = parse_number,
[ "t" ] = parse_literal,
[ "f" ] = parse_literal,
[ "n" ] = parse_literal,
[ "[" ] = parse_array,
[ "{" ] = parse_object,
}
parse = function(str, idx)
local chr = str:sub(idx, idx)
local f = char_func_map[chr]
if f then
return f(str, idx)
end
decode_error(str, idx, "unexpected character '" .. chr .. "'")
end
function json.decode(str)
if type(str) ~= "string" then
error("expected argument of type string, got " .. type(str))
end
local res, idx = parse(str, next_char(str, 1, space_chars, true))
idx = next_char(str, idx, space_chars, true)
if idx <= #str then
decode_error(str, idx, "trailing garbage")
end
return res
end
return json

View File

@ -1,291 +0,0 @@
-- This script generates `snippets.json` from `lua_api.txt`
-- Improvements are welcome.
local json = require("json")
local parse = {}
local globals = {"dump", "dump2"}
function table.exists(t, value)
for k, v in pairs(t) do
if v == value then
return true, k
end
end
end
-- Cut out the useless stuff
parse.trim = function()
local sections = {
"Texture modifiers",
"Node paramtypes",
"Node drawtypes",
"Elements",
"Escape sequences",
"Spatial Vectors",
"Helper functions",
"Translate a string",
"Methods",
"VoxelArea",
"Registered entities",
"Utilities",
"Logging",
"Registration functions",
"Global callback registration functions",
"Setting-related",
"Authentication",
"Chat",
"Environment access",
"Mod channels",
"Inventory",
"Formspec",
"Item handling",
"Rollback",
"Defaults for the `on_place` and `on_drop` item definition functions",
"Defaults for the `on_punch` and `on_dig` node definition callbacks",
"Sounds",
"Timing",
"Server",
"Bans",
"Particles",
"Schematics",
"HTTP Requests",
"Storage API",
"Misc.",
"Global tables",
"`AreaStore`",
"`InvRef`",
"`ItemStack`",
"`ItemStackMetaRef`",
"`MetaDataRef`",
"`ModChannel`",
"`NodeMetaRef`",
"`NodeTimerRef`",
"`ObjectRef`",
"`PcgRandom`",
"`PerlinNoise`",
"`PerlinNoiseMap`",
"`PseudoRandom`",
"`Raycast`",
"`SecureRandom`",
"`Settings`",
"Object properties",
"Entity definition",
"ABM (ActiveBlockModifier) definition",
"LBM (LoadingBlockModifier) definition",
"Item definition",
"Node definition",
"Crafting recipes",
"Ore definition",
"Biome definition",
"Decoration definition",
"Chat command definition",
"Privilege definition",
"Detached inventory callbacks",
"HUD Definition",
"Particle definition",
"`ParticleSpawner` definition",
"`HTTPRequest` definition",
"`HTTPRequestResult` definition",
"Authentication handler definition",
}
local file = io.open("trimmed.txt", "w")
local queue = {}
local section = ""
for line in io.lines("lua_api.txt") do
queue[#queue + 1] = line
if queue[2] and queue[2]:match("^[=-]+$") then
if table.exists(sections, queue[1]) then
section = queue[1]
else
section = ""
end
end
if section ~= "" then
file:write("\n" .. queue[1])
end
if #queue == 3 then
table.remove(queue, 1)
end
end
for _, line in pairs(queue) do
file:write("\n" .. line)
end
file:close()
end
-- Put each type of snippet in its own file
parse.consolodate = function()
local types = {
functions = function(line)
return line:match("^%* `%w") and line:match("%)`")
end,
modifiers = function(line)
return line:match("^#### `%[")
end,
elements = function(line)
return line:match("^### `")
end,
tables = function(line)
return line:match("^%* `minetest%.[%w_]+`")
end,
}
for t, m in pairs(types) do
local file = io.open(t .. ".txt", "w")
for line in io.lines("trimmed.txt") do
if m(line) then
file:write("\n" .. line)
end
end
file:close()
end
end
-- Convert each file to snippets
parse.snippets = function()
local types = {
functions = function(line, list, snippets, current)
if table.exists(list, line) then
local token = ":"
if line:match("`%w+%.") or (line:match("`([%w_-]+)%(") and table.exists(globals, line:match("`([%w_-]+)%("))) then
token = line:match("`(%w+)%.")
end
snippets[#snippets + 1] = {
prefix = line:match("([%w_-]+)%("),
body = line:match("%* `(%w*%.?.-)`"):gsub("^%w+%.", ""):gsub("(%(.*%))", function(args)
local a = 0
return args:gsub("[%w%s_-]+", function(arg)
a = a + 1
return ("${%s:%s}"):format(a, arg:gsub("^ ", ""))
end):gsub(",", ", ")
end),
desc = (line:match("`:? (.*)") or ""):gsub("\"", "\\\""),
kind = 2,
detail = line:match("%* `(.-)`"),
token = token,
}
current = true
elseif current and not line:match("^%s*$") then
if snippets[#snippets].desc ~= "" then
line = "\n" .. line
end
snippets[#snippets].desc = snippets[#snippets].desc .. line
else
current = false
end
return current
end,
modifiers = function(line, list, snippets, current)
if table.exists(list, line) then
snippets[#snippets + 1] = {
prefix = line:match("`%[(.-)[`:]"),
body = line:match("#### `%[(.-)`"):gsub("(.*)", function(args)
local a = 0
return args:gsub("<(.-)>", function(arg)
a = a + 1
return ("${%s:%s}"):format(a, arg:gsub("^ ", ""))
end)
end),
desc = "",
kind = 13,
detail = line:match("`%[(.-)[`:]"),
token = "[",
}
current = 1
elseif current and line:match("^%w") and not line:match("^Example:") then
if snippets[#snippets].desc ~= "" then
line = "\n" .. line
end
snippets[#snippets].desc = snippets[#snippets].desc .. line
current = 2
elseif current == 2 then
current = false
end
return current
end,
elements = function(line, list, snippets, current)
if table.exists(list, line) then
snippets[#snippets + 1] = {
prefix = line:match("`(.-)%["),
body = line:match("### `(.-)`"):gsub("(.*)", function(args)
local a = 0
return args:gsub("<(.-)>", function(arg)
a = a + 1
return ("${%s:%s}"):format(a, arg:gsub("^ ", ""))
end)
end),
desc = "",
kind = 13,
detail = line:match("`(.-)`"):gsub("[<>]", ""),
token = "", -- Lack of token means elements wont be autocompleted
}
current = 1
elseif current and line:match("^%*") then
if snippets[#snippets].desc ~= "" then
line = "\n" .. line
end
snippets[#snippets].desc = snippets[#snippets].desc .. line
current = 2
elseif current == 2 then
current = false
end
return current
end,
tables = function(line, list, snippets, current)
if table.exists(list, line) then
snippets[#snippets + 1] = {
prefix = line:match("`%w+%.(.-)`"),
body = line:match("`%w+%.(.-)`"),
desc = "",
kind = 5,
detail = line:match("`(.-)`"),
token = "minetest",
}
current = true
elseif current then
snippets[#snippets].desc = snippets[#snippets].desc .. line
current = false
end
return current
end,
}
local snippets = {}
for t, exec in pairs(types) do
local list = {}
for line in io.lines(t .. ".txt") do
if line ~= "" then
list[#list + 1] = line
end
end
os.remove(t .. ".txt")
local static = false
for line in io.lines("trimmed.txt") do
static = exec(line, list, snippets, static)
end
end
os.remove("trimmed.txt")
local file = io.open("../snippets.json", "w")
file:write(json.encode(snippets))
file:close()
end
-- Do the things
parse.trim()
parse.consolodate()
parse.snippets()

File diff suppressed because it is too large Load Diff

View File

@ -57,6 +57,12 @@
}
}
],
"snippets": [
{
"language": "lua",
"path": "./snippets.json"
}
],
"grammars": [
{
"scopeName": "source.formspec",

1
smartsnippets.json Normal file

File diff suppressed because one or more lines are too long

95
snippets.js Normal file
View File

@ -0,0 +1,95 @@
const fs = require("fs");
function docLink(api, search) {
const line = api.substring(0, api.indexOf(search)).split("\n").length;
return line > 1 ? `\n\n[View in lua_api.txt](https://github.com/minetest/minetest/blob/5.4.1/doc/lua_api.txt#L${line}-L${line + search.split("\n").length - 1})` : ""
}
let types = [];
const objects = (entry, api) => {
entry = entry.substr(1);
// The documented thing (* `between here`:)
const name = entry.match(/^\* `([^\n`:]+)/)[1];
// The specific name (* `minetest.justthisfunction(but, not, the, args)`:)
const lookfor = name.match(/(?:\S+?\.)?([^\(]+)/)[1];
// The completed thing without namespaces and with argument tabstops if applicable
let i = 0;
const complete = name.match(/([^.]*)$/)[0].replace(/\(([^(]+)\)/, args => {
return args.replace(/(?! )[\w\- ]+/g, arg => `\${${++i}:${arg}}`);
}).replace(/(\(function\(.+)\)$/, `$1\n\t$${++i}\nend)`) + "$0";
// Full method documentation
const doc = entry.match(/^\* ([\S\s]*?)\n*$/)[1].replace(/\n /g, "\n") + docLink(api, entry); // Remove extra indent
// Type of thing
const type = name.match(/\(/) && (name.match(/\./) && "Function" || "Method") || "Object";
// Namespace or method character (:)
const namespace = ((name.match(/^HTTPApiTable/) && {1: "."}) || name.match(/^(\S*?\.)/) || {1: ":"})[1];
return {
prefix: lookfor, // look for this
body: complete, // set to this
desc: doc, // documentation description
kind: {Function: 2, Method: 1, Object: 5}[type], // this type
detail: type, // header detail
token: namespace, // look after this
};
}
// Functions and methods
types.push([/\n\* `(?!dump)[^\(][^`:]+\).*(?:\n+ +.+)*/g, objects]);
// Lists and objects
types.push([/\n\* `minetest\.(?!conf)(?![A-Z_\-]+?`)[^\(\)]+?`.*(?:\n+ +.+)*/g, objects]);
// Formspec elements
types.push([/### `.+\[.*\n?\n[\S\s]+?\n\n/g, (entry, api) => {
return {
prefix: entry.match(/^### `(.+?)\[/)[1],
body: entry.match(/^### `(.+?\])/)[1].replace(/<(.+?)>/g, "$1") + "$0",
desc: entry.match(/^([\S\s]*?)\n*$/)[1] + docLink(api, entry),
kind: 13,
detail: "Formspec Element",
// token: entry.match(/^### `(.)/)[1],
};
}]);
// Texture modifiers
types.push([/#### `\[.+\n\n(?:[^\n]+\n+(?!(?:####)|(?:\-+\n)))+/g, (entry, api) => {
let i = 0;
return {
prefix: entry.match(/\[([\w_-]+)/)[1],
body: entry.match(/\[(.+?)`/)[1].replace(/<(.+?)>|(\.\.\.)/g, (arg1, arg2) => `\${${++i}:${arg1 || arg2}}`) + "$0",
desc: entry.match(/^([\S\s]+?)\n*$/)[1] + docLink(api, entry),
kind: 7,
detail: "Texture Modifier",
token: "[",
};
}]);
// Constants
types.push([/[`']minetest\.[A-Z_\-]+[`'](?:.{5,}|.{0})/g, (entry, api) => {
return {
prefix: entry.match(/\.(.+?)[`']/)[1],
body: entry.match(/[`'](.+?)[`']/)[1],
desc: entry.replace(/'/g, "`") + docLink(api, entry),
kind: 11,
detail: "Constant",
token: "minetest.",
};
}]);
function getSnippets(api) {
let snippets = [];
for (const type of types) {
for (let entry of api.matchAll(type[0])) {
snippets.push(type[1](entry[0], api));
}
}
return snippets;
}
fs.readFile("./lua_api.txt", "utf8", (err, data) => {
if (!err) fs.writeFile("./smartsnippets.json", JSON.stringify(getSnippets(data)), "utf8", () => {});
});

File diff suppressed because one or more lines are too long