diff --git a/.luarc.json b/.luarc.json new file mode 100644 index 0000000000..d06245585f --- /dev/null +++ b/.luarc.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", + "runtime": { + "version": "LuaJIT" + }, + "workspace": { + "library": [ + "$VIMRUNTIME", + "${3rd}/luv/library" + ], + "checkThirdParty": false + } +} diff --git a/.stylua.toml b/.stylua.toml new file mode 100644 index 0000000000..c4b052ab72 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,5 @@ +column_width = 80 +indent_type = "Spaces" +indent_width = 2 +quote_style = "AutoPreferDouble" +call_parentheses = "None" diff --git a/lua/vimtex/bibparser.lua b/lua/vimtex/bibparser.lua new file mode 100644 index 0000000000..477dd479b1 --- /dev/null +++ b/lua/vimtex/bibparser.lua @@ -0,0 +1,240 @@ +local M = {} + +local function empty(list) + if type(list) == "table" then + return next(list) == nil + end + + if type(list) == "string" then + return list == "" + end + + return false +end + +local function format_line(title, object) + return "**" .. string.upper(title) .. "**" .. ": " .. object .. "\n" +end + +local function format_entry(entry) + local info = "" + local _entry = {} + local prioritized_keys = { "title", "author" } + + for _, key in pairs(prioritized_keys) do + if entry[key] ~= nil then + info = info .. format_line(key, entry[key]) + end + end + + local i = 0 + for k, v in pairs(entry) do + if not vim.tbl_contains(prioritized_keys, k) then + i = i + 1 + _entry[i] = format_line(k, v) + end + end + table.sort(_entry) + + for _, a in ipairs(_entry) do + info = info .. a + end + + return info +end + +---@param head 0-index +local function get_key(body, head) + local matches = vim.fn.matchlist(body, [[^\v([-_:0-9a-zA-Z]+)\s*\=\s*]], head) + if empty(matches) then + return "", -1 + end + + return string.lower(matches[2]), head + vim.fn.strlen(matches[1]) +end + +local function parse_string(line, current, strings) + current.level = current.level + + vim.fn["cmp_vimtex#count"](line, "{") + - vim.fn["cmp_vimtex#count"](line, "}") + if current.level > 0 then + current.body = current.body .. line + return + end + + current.body = current.body .. vim.fn.matchstr(line, [[.*\ze}]]) + + local matches = + vim.fn.matchlist(current.body, [[\v^\s*(\w+)\s*\=\s*"(.*)"\s*$]]) + if not empty(matches) and not empty(matches[2]) then + strings[matches[2]] = matches[3] + end + current = {} +end + +local function parse_entry(line, current, entries) + current.level = current.level + + vim.fn["cmp_vimtex#count"](line, "{") + - vim.fn["cmp_vimtex#count"](line, "}") + if current.level > 0 then + current.body = current.body .. line + return + end + + current.body = current.body .. vim.fn.matchstr(line, [[.*\ze}]]) + + table.insert(entries, current) + current = {} +end + +local function parse_type(file, lnum, line, current, strings, entries) + local matches = vim.fn.matchlist(line, [[\v^\@(\w+)\s*\{\s*(.*)]]) + if empty(matches) then + return + end + + local type = string.lower(matches[2]) + local types = { preamble = 1, comment = 1 } + if types[type] ~= nil then + return + end + + current.level = 1 + current.body = "" + current.vimtex_file = file + current.vimtex_lnum = lnum + + if type == "string" then + parse_string(matches[3], current, strings) + end + + matches = vim.fn.matchlist(matches[3], [[\v^([^, ]*)\s*,\s*(.*)]]) + current.type = type + current.key = matches[2] + + if empty(matches[3]) then + return + end + + parse_entry(matches[3], current, entries) +end + +---@param head 0-index +local function get_value_string(body, head, strings) + local value + local head_1 + if body:sub(head + 1, head + 1) == "{" then + local sum = 1 + local i1 = head + 1 + local i0 = i1 + + while sum > 0 do + local match + local res = vim.fn.matchstrpos(body, [=[[{}]]=], i1) + match, _, i1 = res[1], res[2], res[3] + res = nil + + if i1 < 0 then + break + end + + i0 = i1 + sum = sum + (match == "{" and 1 or -1) + end + + value = body:sub(head + 1 + 1, i0 - 2 + 1) + head_1 = vim.fn.matchend(body, [[^\s*]], i0) + elseif body:sub(head + 1, head + 1) == [["]] then + local index = vim.fn.match(body, [[\\\@= 0 do + if empty(key) then + key, pos = get_key(entry.body, pos) + else + local value + value, pos = get_value(entry.body, pos) + entry[key] = value + key = "" + end + end + + entry.body = nil + return entry +end + +M.parse = function(file) + if file == nil or not vim.fn.filereadable(file) then + return {} + end + + local lines = vim.fn.readfile(file) + local entries = {} + local strings = {} + local current = {} + + for lnum = 1, #lines do + local line = lines[lnum] + + if empty(current) then + parse_type(lnum, line, file, current, strings, entries) + goto continue + end + + if current.type == "string" then + parse_string(line) + else + parse_entry(line, current, entries) + end + + ::continue:: + end + + local result = {} + for _, v in pairs(entries) do + result[v.key] = parse_entry_body(v) + result[v.key].formatted_info = format_entry(result[v.key]) + end + + return result +end + +return M