From 8fe79c2783bcde10595267226cbb4d7ec2c6b865 Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Mon, 30 Sep 2024 16:18:19 +0200 Subject: [PATCH] fix: Fix fallback behavior; apply stylua --- lua/jsonfly/cache.lua | 45 +- lua/jsonfly/insert.lua | 516 +++++++------- lua/jsonfly/json.lua | 937 +++++++++++++------------- lua/jsonfly/parsers.lua | 225 +++---- lua/jsonfly/utils.lua | 208 +++--- lua/telescope/_extensions/jsonfly.lua | 349 +++++----- 6 files changed, 1134 insertions(+), 1146 deletions(-) diff --git a/lua/jsonfly/cache.lua b/lua/jsonfly/cache.lua index 42551de..7b90be4 100644 --- a/lua/jsonfly/cache.lua +++ b/lua/jsonfly/cache.lua @@ -1,47 +1,42 @@ -local M = {}; +local M = {} -local _cache = {}; +local _cache = {} ---@param buffer integer function M:cache_buffer(buffer, value) - _cache[buffer] = value; + _cache[buffer] = value end ---@param buffer integer function M:invalidate_buffer(buffer) - _cache[buffer] = nil; + _cache[buffer] = nil end ---@param buffer integer ---@return string[]|nil function M:get_cache(buffer) - return _cache[buffer]; + return _cache[buffer] end -local _listening_buffers = {}; +local _listening_buffers = {} ---@param buffer integer function M:register_listeners(buffer) - if _listening_buffers[buffer] then - return; - end + if _listening_buffers[buffer] then + return + end - _listening_buffers[buffer] = true; + _listening_buffers[buffer] = true - vim.api.nvim_buf_attach( - buffer, - false, - { - on_lines = function() - self:invalidate_buffer(buffer) - end, - on_detach = function() - self:invalidate_buffer(buffer) - _listening_buffers[buffer] = nil; - end, - } - ); + vim.api.nvim_buf_attach(buffer, false, { + on_lines = function() + self:invalidate_buffer(buffer) + end, + on_detach = function() + self:invalidate_buffer(buffer) + _listening_buffers[buffer] = nil + end, + }) end -return M; - +return M diff --git a/lua/jsonfly/insert.lua b/lua/jsonfly/insert.lua index 9f3418f..f4576b4 100644 --- a/lua/jsonfly/insert.lua +++ b/lua/jsonfly/insert.lua @@ -1,42 +1,42 @@ -local utils = require"jsonfly.utils" +local utils = require("jsonfly.utils") -- This string will be used to position the cursor properly. -- Once everything is set, the cursor searches for this string and jumps to it. -- After that, it will be removed immediately. local CURSOR_SEARCH_HELPER = "_jsonFfFfFfLyY0904857CursorHelperRrRrRrR" -local M = {}; +local M = {} -- https://stackoverflow.com/a/24823383/9878135 function table.slice(tbl, first, last, step) - local sliced = {} + local sliced = {} - for i = first or 1, last or #tbl, step or 1 do - sliced[#sliced+1] = tbl[i] - end + for i = first or 1, last or #tbl, step or 1 do + sliced[#sliced + 1] = tbl[i] + end - return sliced + return sliced end ---@param line string ---@param also_match_end_bracket boolean - Whether to also match only a closing bracket ---@return boolean - Whether the line contains an empty JSON object local function line_contains_empty_json(line, also_match_end_bracket) - -- Starting and ending on same line - return string.match(line, ".*[%{%[]%s*[%}%]]%s*,?*%s*") - -- Opening bracket on line - or string.match(line, ".*[%{%[]%s*") - -- Closing bracket on line - or (also_match_end_bracket and string.match(line, ".*.*[%}%]]%s*,?%s*")) + -- Starting and ending on same line + return string.match(line, ".*[%{%[]%s*[%}%]]%s*,?*%s*") + -- Opening bracket on line + or string.match(line, ".*[%{%[]%s*") + -- Closing bracket on line + or (also_match_end_bracket and string.match(line, ".*.*[%}%]]%s*,?%s*")) end ---@param entry Entry ---@param key string ---@param index number local function check_key_equal(entry, key, index) - local splitted = utils:split_by_char(entry.key, ".") + local splitted = utils:split_by_char(entry.key, ".") - return splitted[index] == key + return splitted[index] == key end ---Find the entry in `entries` with the most matching keys at the beginning based on the `keys`. @@ -45,214 +45,196 @@ end ---@param keys string[] ---@return number|nil local function find_best_fitting_entry(entries, keys) - local entry_index - local current_indexes = {1, #entries} + local entry_index + local current_indexes = { 1, #entries } - for kk=1, #keys do - local key = keys[kk] + for kk = 1, #keys do + local key = keys[kk] - local start_index = current_indexes[1] - local end_index = current_indexes[2] + local start_index = current_indexes[1] + local end_index = current_indexes[2] - current_indexes = {nil, nil} + current_indexes = { nil, nil } - for ii=start_index, end_index do - if check_key_equal(entries[ii], key, kk) then - if current_indexes[1] == nil then - current_indexes[1] = ii - end + for ii = start_index, end_index do + if check_key_equal(entries[ii], key, kk) then + if current_indexes[1] == nil then + current_indexes[1] = ii + end - current_indexes[2] = ii - end - end + current_indexes[2] = ii + end + end - if current_indexes[1] == nil then - -- No entries found - break - else - entry_index = current_indexes[1] - end - end + if current_indexes[1] == nil then + -- No entries found + break + else + entry_index = current_indexes[1] + end + end - return entry_index + return entry_index end ---@param keys KeyDescription ---@param index number - Index of the key ---@param lines string[] - Table to write the lines to local function write_keys(keys, index, lines) - local key = keys[index] + local key = keys[index] - if index == #keys then - lines[#lines + 1] = "\"" .. key.key .. "\": \"" .. CURSOR_SEARCH_HELPER .. "\"" - return - end + if index == #keys then + lines[#lines + 1] = '"' .. key.key .. '": "' .. CURSOR_SEARCH_HELPER .. '"' + return + end - if key.type == "object_wrapper" then - local previous_line = lines[#lines] or "" - if line_contains_empty_json(previous_line, true) or #lines == 0 then - lines[#lines + 1] = "{" - else - lines[#lines] = previous_line .. " {" - end + if key.type == "object_wrapper" then + local previous_line = lines[#lines] or "" + if line_contains_empty_json(previous_line, true) or #lines == 0 then + lines[#lines + 1] = "{" + else + lines[#lines] = previous_line .. " {" + end - write_keys(keys, index + 1, lines) + write_keys(keys, index + 1, lines) - lines[#lines + 1] = "}" - elseif key.type == "key" then - lines[#lines + 1] = "\"" .. key.key .. "\":" + lines[#lines + 1] = "}" + elseif key.type == "key" then + lines[#lines + 1] = '"' .. key.key .. '":' - write_keys(keys, index + 1, lines) - elseif key.type == "array_wrapper" then - local previous_line = lines[#lines] or "" - -- Starting and ending on same line - if line_contains_empty_json(previous_line, true) or #lines == 0 then - lines[#lines + 1] = "[" - else - lines[#lines] = previous_line .. " [" - end - write_keys(keys, index + 1, lines) + write_keys(keys, index + 1, lines) + elseif key.type == "array_wrapper" then + local previous_line = lines[#lines] or "" + -- Starting and ending on same line + if line_contains_empty_json(previous_line, true) or #lines == 0 then + lines[#lines + 1] = "[" + else + lines[#lines] = previous_line .. " [" + end + write_keys(keys, index + 1, lines) - lines[#lines + 1] = "]" - elseif key.type == "array_index" then - local amount = tonumber(key.key) - -- Write previous empty array objects - for _=1, amount do - lines[#lines + 1] = "{}," - end + lines[#lines + 1] = "]" + elseif key.type == "array_index" then + local amount = tonumber(key.key) + -- Write previous empty array objects + for _ = 1, amount do + lines[#lines + 1] = "{}," + end - write_keys(keys, index + 1, lines) - end + write_keys(keys, index + 1, lines) + end end ---@param buffer number ---@param insertion_line number local function add_comma(buffer, insertion_line) - local BUFFER_SIZE = 5 + local BUFFER_SIZE = 5 - -- Find next non-empty character in reverse - for ii=insertion_line, 0, -BUFFER_SIZE do - local previous_lines = vim.api.nvim_buf_get_lines( - buffer, - math.max(0, ii - BUFFER_SIZE), - ii, - false - ) + -- Find next non-empty character in reverse + for ii = insertion_line, 0, -BUFFER_SIZE do + local previous_lines = vim.api.nvim_buf_get_lines(buffer, math.max(0, ii - BUFFER_SIZE), ii, false) - if #previous_lines == 0 then - return - end + if #previous_lines == 0 then + return + end - for jj=#previous_lines, 1, -1 do - local line = previous_lines[jj] + for jj = #previous_lines, 1, -1 do + local line = previous_lines[jj] - for char_index=#line, 1, -1 do - local char = line:sub(char_index, char_index) + for char_index = #line, 1, -1 do + local char = line:sub(char_index, char_index) - if char ~= " " and char ~= "\t" and char ~= "\n" and char ~= "\r" then - if char == "," or char == "{" or char == "[" then - return - end + if char ~= " " and char ~= "\t" and char ~= "\n" and char ~= "\r" then + if char == "," or char == "{" or char == "[" then + return + end - -- Insert comma at position - local line_number = math.max(0, ii - BUFFER_SIZE) + jj - 1 - vim.api.nvim_buf_set_text( - buffer, - line_number, - char_index, - line_number, - char_index, - {","} - ) - return - end - end - end - end + -- Insert comma at position + local line_number = math.max(0, ii - BUFFER_SIZE) + jj - 1 + vim.api.nvim_buf_set_text(buffer, line_number, char_index, line_number, char_index, { "," }) + return + end + end + end + end end ---@return number - The new line number to be used, as the buffer has been modified local function expand_empty_object(buffer, line_number) - local line = vim.api.nvim_buf_get_lines(buffer, line_number, line_number + 1, false)[1] or "" + local line = vim.api.nvim_buf_get_lines(buffer, line_number, line_number + 1, false)[1] or "" - if line_contains_empty_json(line, false) then - local position_closing_bracket = string.find(line, "[%}%]]") - local remaining_line = string.sub(line, position_closing_bracket + 1) + if line_contains_empty_json(line, false) then + local position_closing_bracket = string.find(line, "[%}%]]") + local remaining_line = string.sub(line, position_closing_bracket + 1) - vim.api.nvim_buf_set_lines( - buffer, - line_number, - line_number + 1, - false, - { - "{", - "}" .. remaining_line - } - ) + vim.api.nvim_buf_set_lines(buffer, line_number, line_number + 1, false, { + "{", + "}" .. remaining_line, + }) - return line_number + 1 - end + return line_number + 1 + end - return line_number + return line_number end ---@param keys KeyDescription[] ---@param input_key_depth number local function get_key_descriptor_index(keys, input_key_depth) - local depth = 0 - local index = 0 + local depth = 0 + local index = 0 - for ii=1, #keys do - if keys[ii].type == "key" or keys[ii].type == "array_index" then - depth = depth + 1 - end + for ii = 1, #keys do + if keys[ii].type == "key" or keys[ii].type == "array_index" then + depth = depth + 1 + end - if depth >= input_key_depth then - index = ii - break - end - end + if depth >= input_key_depth then + index = ii + break + end + end - return index + return index end ---@param entries Entry[] ---@param keys string[] ---@return integer|nil - The index of the entry local function get_entry_by_keys(entries, keys) - for ii=1, #entries do - local entry = entries[ii] - local splitted = utils:split_by_char(entry.key, ".") + for ii = 1, #entries do + local entry = entries[ii] + local splitted = utils:split_by_char(entry.key, ".") - local found = true + local found = true - for jj=1, #keys do - if keys[jj] ~= splitted[jj] then - found = false - break - end - end + for jj = 1, #keys do + if keys[jj] ~= splitted[jj] then + found = false + break + end + end - if found then - return ii - end - end + if found then + return ii + end + end end ---@param keys KeyDescription[] ---@return string[] local function flatten_key_description(keys) - local flat_keys = {} + local flat_keys = {} - for ii=1, #keys do - if keys[ii].type == "key" then - flat_keys[#flat_keys + 1] = keys[ii].key - elseif keys[ii].type == "array_index" then - flat_keys[#flat_keys + 1] = tostring(keys[ii].key) - end - end + for ii = 1, #keys do + if keys[ii].type == "key" then + flat_keys[#flat_keys + 1] = keys[ii].key + elseif keys[ii].type == "array_index" then + flat_keys[#flat_keys + 1] = tostring(keys[ii].key) + end + end - return flat_keys + return flat_keys end ---Subtracts indexes if there are other indexes before already @@ -262,43 +244,43 @@ end ---@param starting_keys KeyDescription[] ---@param key KeyDescription - Th key to be inserted; must be of type `array_index`; will be modified in-place local function normalize_array_indexes(entries, starting_keys, key) - local starting_keys_flat = flatten_key_description(starting_keys) - local starting_key_index = get_entry_by_keys(entries, starting_keys_flat) - local entry = entries[starting_key_index] + local starting_keys_flat = flatten_key_description(starting_keys) + local starting_key_index = get_entry_by_keys(entries, starting_keys_flat) + local entry = entries[starting_key_index] - key.key = key.key - #entry.value + key.key = key.key - #entry.value end ---@param entries Entry[] - Entries, they must be children of a top level array ---Counts how many top level children an array has local function count_array_children(entries) - for ii=1, #entries do - if string.match(entries[ii].key, "^%d+$") then - return ii - end - end + for ii = 1, #entries do + if string.match(entries[ii].key, "^%d+$") then + return ii + end + end - return #entries + return #entries end ---Jump to the cursor helper and remove it ---@param buffer number function M:jump_to_cursor_helper(buffer) - vim.fn.search(CURSOR_SEARCH_HELPER) + vim.fn.search(CURSOR_SEARCH_HELPER) - -- Remove cursor helper - local position = vim.api.nvim_win_get_cursor(0) - vim.api.nvim_buf_set_text( - buffer, - position[1] - 1, - position[2], - position[1] - 1, - position[2] + #CURSOR_SEARCH_HELPER, - {""} - ) + -- Remove cursor helper + local position = vim.api.nvim_win_get_cursor(0) + vim.api.nvim_buf_set_text( + buffer, + position[1] - 1, + position[2], + position[1] - 1, + position[2] + #CURSOR_SEARCH_HELPER, + { "" } + ) - -- -- Go into insert mode - vim.cmd [[execute "normal a"]] + -- -- Go into insert mode + vim.cmd([[execute "normal a"]]) end -- TODO: Handle top level empty arrays @@ -306,107 +288,107 @@ end ---@param keys KeyDescription[] ---@param buffer number function M:insert_new_key(entries, keys, buffer) - -- Close current buffer - vim.cmd [[quit!]] + -- Close current buffer + vim.cmd([[quit!]]) - local input_key = flatten_key_description(keys) - ---@type boolean - local should_add_comma = true + local input_key = flatten_key_description(keys) + ---@type boolean + local should_add_comma = true - ---@type KeyDescription[] - local remaining_keys - ---@type Entry - local entry + ---@type KeyDescription[] + local remaining_keys + ---@type Entry + local entry - if #entries == 0 then - remaining_keys = table.slice(keys, 2, #keys) + if #entries == 0 then + remaining_keys = table.slice(keys, 2, #keys) - entry = { - key = "", - position = { - key_start = 1, - line_number = 1, - value_start = 1 - } - } - should_add_comma = false - else - local entry_index = find_best_fitting_entry(entries, input_key) or 0 - entry = entries[entry_index] + entry = { + key = "", + position = { + key_start = 1, + line_number = 1, + value_start = 1, + }, + } + should_add_comma = false + else + local entry_index = find_best_fitting_entry(entries, input_key) or 0 + entry = entries[entry_index] - ---@type integer - local existing_keys_index + ---@type integer + local existing_keys_index - if entry == nil then - -- Insert as root - existing_keys_index = 0 - remaining_keys = table.slice(keys, 2, #keys) + if entry == nil then + -- Insert as root + existing_keys_index = 0 + remaining_keys = table.slice(keys, 2, #keys) - -- Top level array - if entries[1].key == "0" then - -- Normalize array indexes - remaining_keys[1].key = remaining_keys[1].key - count_array_children(entries) - end + -- Top level array + if entries[1].key == "0" then + -- Normalize array indexes + remaining_keys[1].key = remaining_keys[1].key - count_array_children(entries) + end - entry = { - key = "", - position = { - key_start = 1, - line_number = 1, - value_start = 1 - } - } - else - local existing_input_keys_depth = #utils:split_by_char(entry.key, ".") + 1 - existing_keys_index = get_key_descriptor_index(keys, existing_input_keys_depth) - remaining_keys = table.slice(keys, existing_keys_index, #keys) + entry = { + key = "", + position = { + key_start = 1, + line_number = 1, + value_start = 1, + }, + } + else + local existing_input_keys_depth = #utils:split_by_char(entry.key, ".") + 1 + existing_keys_index = get_key_descriptor_index(keys, existing_input_keys_depth) + remaining_keys = table.slice(keys, existing_keys_index, #keys) - if remaining_keys[1].type == "array_index" then - local starting_keys = table.slice(keys, 1, existing_keys_index - 1) - normalize_array_indexes(entries, starting_keys, remaining_keys[1]) - end - end - end + if remaining_keys[1].type == "array_index" then + local starting_keys = table.slice(keys, 1, existing_keys_index - 1) + normalize_array_indexes(entries, starting_keys, remaining_keys[1]) + end + end + end - local _writes = {} - write_keys(remaining_keys, 1, _writes) - local writes = {} + local _writes = {} + write_keys(remaining_keys, 1, _writes) + local writes = {} - for ii=1, #_writes do - if _writes[ii] == true then - -- Unwrap table - writes[#writes] = writes[#writes][1] - else - writes[#writes + 1] = _writes[ii] - end - end + for ii = 1, #_writes do + if _writes[ii] == true then + -- Unwrap table + writes[#writes] = writes[#writes][1] + else + writes[#writes + 1] = _writes[ii] + end + end - -- Hacky way to jump to end of object - vim.api.nvim_win_set_cursor(0, {entry.position.line_number, entry.position.value_start}) - vim.cmd [[execute "normal %"]] + -- Hacky way to jump to end of object + vim.api.nvim_win_set_cursor(0, { entry.position.line_number, entry.position.value_start }) + vim.cmd([[execute "normal %"]]) - local changes = #writes - local start_line = vim.api.nvim_win_get_cursor(0)[1] - 1 + local changes = #writes + local start_line = vim.api.nvim_win_get_cursor(0)[1] - 1 - -- Add comma to previous JSON entry - if should_add_comma then - add_comma(buffer, start_line) - end - local new_start_line = expand_empty_object(buffer, start_line) + -- Add comma to previous JSON entry + if should_add_comma then + add_comma(buffer, start_line) + end + local new_start_line = expand_empty_object(buffer, start_line) - if new_start_line ~= start_line then - changes = changes + math.abs(new_start_line - start_line) - start_line = new_start_line - end + if new_start_line ~= start_line then + changes = changes + math.abs(new_start_line - start_line) + start_line = new_start_line + end - -- Insert new lines - vim.api.nvim_buf_set_lines(buffer, start_line, start_line, false, writes) + -- Insert new lines + vim.api.nvim_buf_set_lines(buffer, start_line, start_line, false, writes) - -- Format lines - vim.api.nvim_win_set_cursor(0, {start_line, 1}) - vim.cmd('execute "normal =' .. changes .. 'j"') + -- Format lines + vim.api.nvim_win_set_cursor(0, { start_line, 1 }) + vim.cmd('execute "normal =' .. changes .. 'j"') - M:jump_to_cursor_helper(buffer) + M:jump_to_cursor_helper(buffer) end -return M; +return M diff --git a/lua/jsonfly/json.lua b/lua/jsonfly/json.lua index 145b055..579ef6d 100644 --- a/lua/jsonfly/json.lua +++ b/lua/jsonfly/json.lua @@ -1,611 +1,612 @@ -local VERSION = '20161109.21' -- version history at end of file +local VERSION = "20161109.21" -- version history at end of file local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20161109.21 ]-" local OBJDEF = { - VERSION = VERSION, - AUTHOR_NOTE = AUTHOR_NOTE, + VERSION = VERSION, + AUTHOR_NOTE = AUTHOR_NOTE, } +local default_pretty_indent = " " +local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } - -local default_pretty_indent = " " -local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } - -local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray -local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject +local isArray = { + __tostring = function() + return "JSON array" + end, +} +isArray.__index = isArray +local isObject = { + __tostring = function() + return "JSON object" + end, +} +isObject.__index = isObject function OBJDEF:newArray(tbl) - return setmetatable(tbl or {}, isArray) + return setmetatable(tbl or {}, isArray) end function OBJDEF:newObject(tbl) - return setmetatable(tbl or {}, isObject) + return setmetatable(tbl or {}, isObject) end - - - local function getnum(op) - return type(op) == 'number' and op or op.N + return type(op) == "number" and op or op.N end local isNumber = { - __tostring = function(T) return T.S end, - __unm = function(op) return getnum(op) end, + __tostring = function(T) + return T.S + end, + __unm = function(op) + return getnum(op) + end, - __concat = function(op1, op2) return tostring(op1) .. tostring(op2) end, - __add = function(op1, op2) return getnum(op1) + getnum(op2) end, - __sub = function(op1, op2) return getnum(op1) - getnum(op2) end, - __mul = function(op1, op2) return getnum(op1) * getnum(op2) end, - __div = function(op1, op2) return getnum(op1) / getnum(op2) end, - __mod = function(op1, op2) return getnum(op1) % getnum(op2) end, - __pow = function(op1, op2) return getnum(op1) ^ getnum(op2) end, - __lt = function(op1, op2) return getnum(op1) < getnum(op2) end, - __eq = function(op1, op2) return getnum(op1) == getnum(op2) end, - __le = function(op1, op2) return getnum(op1) <= getnum(op2) end, + __concat = function(op1, op2) + return tostring(op1) .. tostring(op2) + end, + __add = function(op1, op2) + return getnum(op1) + getnum(op2) + end, + __sub = function(op1, op2) + return getnum(op1) - getnum(op2) + end, + __mul = function(op1, op2) + return getnum(op1) * getnum(op2) + end, + __div = function(op1, op2) + return getnum(op1) / getnum(op2) + end, + __mod = function(op1, op2) + return getnum(op1) % getnum(op2) + end, + __pow = function(op1, op2) + return getnum(op1) ^ getnum(op2) + end, + __lt = function(op1, op2) + return getnum(op1) < getnum(op2) + end, + __eq = function(op1, op2) + return getnum(op1) == getnum(op2) + end, + __le = function(op1, op2) + return getnum(op1) <= getnum(op2) + end, } isNumber.__index = isNumber function OBJDEF:asNumber(item) - - if getmetatable(item) == isNumber then - -- it's already a JSON number object. - return item - elseif type(item) == 'table' and type(item.S) == 'string' and type(item.N) == 'number' then - -- it's a number-object table that lost its metatable, so give it one - return setmetatable(item, isNumber) - else - -- the normal situation... given a number or a string representation of a number.... - local holder = { - S = tostring(item), -- S is the representation of the number as a string, which remains precise - N = tonumber(item), -- N is the number as a Lua number. - } - return setmetatable(holder, isNumber) - end + if getmetatable(item) == isNumber then + -- it's already a JSON number object. + return item + elseif type(item) == "table" and type(item.S) == "string" and type(item.N) == "number" then + -- it's a number-object table that lost its metatable, so give it one + return setmetatable(item, isNumber) + else + -- the normal situation... given a number or a string representation of a number.... + local holder = { + S = tostring(item), -- S is the representation of the number as a string, which remains precise + N = tonumber(item), -- N is the number as a Lua number. + } + return setmetatable(holder, isNumber) + end end function OBJDEF:forceString(item) - if type(item) == 'table' and type(item.S) == 'string' then - return item.S - else - return tostring(item) - end + if type(item) == "table" and type(item.S) == "string" then + return item.S + else + return tostring(item) + end end function OBJDEF:forceNumber(item) - if type(item) == 'table' and type(item.N) == 'number' then - return item.N - else - return tonumber(item) - end + if type(item) == "table" and type(item.N) == "number" then + return item.N + else + return tonumber(item) + end end - local function unicode_codepoint_as_utf8(codepoint) - -- - -- codepoint is a number - -- - if codepoint <= 127 then - return string.char(codepoint) + -- + -- codepoint is a number + -- + if codepoint <= 127 then + return string.char(codepoint) + elseif codepoint <= 2047 then + -- + -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 + -- + local highpart = math.floor(codepoint / 0x40) + local lowpart = codepoint - (0x40 * highpart) + return string.char(0xC0 + highpart, 0x80 + lowpart) + elseif codepoint <= 65535 then + -- + -- 1110yyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x1000) + local remainder = codepoint - 0x1000 * highpart + local midpart = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midpart - elseif codepoint <= 2047 then - -- - -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 - -- - local highpart = math.floor(codepoint / 0x40) - local lowpart = codepoint - (0x40 * highpart) - return string.char(0xC0 + highpart, - 0x80 + lowpart) + highpart = 0xE0 + highpart + midpart = 0x80 + midpart + lowpart = 0x80 + lowpart - elseif codepoint <= 65535 then - -- - -- 1110yyyy 10yyyyxx 10xxxxxx - -- - local highpart = math.floor(codepoint / 0x1000) - local remainder = codepoint - 0x1000 * highpart - local midpart = math.floor(remainder / 0x40) - local lowpart = remainder - 0x40 * midpart + -- + -- Check for an invalid character (thanks Andy R. at Adobe). + -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 + -- + if + (highpart == 0xE0 and midpart < 0xA0) + or (highpart == 0xED and midpart > 0x9F) + or (highpart == 0xF0 and midpart < 0x90) + or (highpart == 0xF4 and midpart > 0x8F) + then + return "?" + else + return string.char(highpart, midpart, lowpart) + end + else + -- + -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx + -- + local highpart = math.floor(codepoint / 0x40000) + local remainder = codepoint - 0x40000 * highpart + local midA = math.floor(remainder / 0x1000) + remainder = remainder - 0x1000 * midA + local midB = math.floor(remainder / 0x40) + local lowpart = remainder - 0x40 * midB - highpart = 0xE0 + highpart - midpart = 0x80 + midpart - lowpart = 0x80 + lowpart - - -- - -- Check for an invalid character (thanks Andy R. at Adobe). - -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 - -- - if ( highpart == 0xE0 and midpart < 0xA0 ) or - ( highpart == 0xED and midpart > 0x9F ) or - ( highpart == 0xF0 and midpart < 0x90 ) or - ( highpart == 0xF4 and midpart > 0x8F ) - then - return "?" - else - return string.char(highpart, - midpart, - lowpart) - end - - else - -- - -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx - -- - local highpart = math.floor(codepoint / 0x40000) - local remainder = codepoint - 0x40000 * highpart - local midA = math.floor(remainder / 0x1000) - remainder = remainder - 0x1000 * midA - local midB = math.floor(remainder / 0x40) - local lowpart = remainder - 0x40 * midB - - return string.char(0xF0 + highpart, - 0x80 + midA, - 0x80 + midB, - 0x80 + lowpart) - end + return string.char(0xF0 + highpart, 0x80 + midA, 0x80 + midB, 0x80 + lowpart) + end end function OBJDEF:onDecodeError(message, text, location, etc) - if text then - if location then - message = string.format("%s at byte %d of: %s", message, location, text) - else - message = string.format("%s: %s", message, text) - end - end + if text then + if location then + message = string.format("%s at byte %d of: %s", message, location, text) + else + message = string.format("%s: %s", message, text) + end + end - if etc ~= nil then - message = message .. " (" .. OBJDEF:encode(etc) .. ")" - end + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end - if self.assert then - self.assert(false, message) - else - assert(false, message) - end + if self.assert then + self.assert(false, message) + else + assert(false, message) + end end function OBJDEF:onTrailingGarbage(json_text, location, parsed_value, etc) - return self:onDecodeError("trailing garbage", json_text, location, etc) + return self:onDecodeError("trailing garbage", json_text, location, etc) end -OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError +OBJDEF.onDecodeOfNilError = OBJDEF.onDecodeError OBJDEF.onDecodeOfHTMLError = OBJDEF.onDecodeError function OBJDEF:onEncodeError(message, etc) - if etc ~= nil then - message = message .. " (" .. OBJDEF:encode(etc) .. ")" - end + if etc ~= nil then + message = message .. " (" .. OBJDEF:encode(etc) .. ")" + end - if self.assert then - self.assert(false, message) - else - assert(false, message) - end + if self.assert then + self.assert(false, message) + else + assert(false, message) + end end local function grok_number(self, text, start, options) - -- - -- Grab the integer part - -- - local integer_part = text:match('^-?[1-9]%d*', start) - or text:match("^-?0", start) + -- + -- Grab the integer part + -- + local integer_part = text:match("^-?[1-9]%d*", start) or text:match("^-?0", start) - if not integer_part then - self:onDecodeError("expected number", text, start, options.etc) - return nil, start -- in case the error method doesn't abort, return something sensible - end + if not integer_part then + self:onDecodeError("expected number", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end - local i = start + integer_part:len() + local i = start + integer_part:len() - -- - -- Grab an optional decimal part - -- - local decimal_part = text:match('^%.%d+', i) or "" + -- + -- Grab an optional decimal part + -- + local decimal_part = text:match("^%.%d+", i) or "" - i = i + decimal_part:len() + i = i + decimal_part:len() - -- - -- Grab an optional exponential part - -- - local exponent_part = text:match('^[eE][-+]?%d+', i) or "" + -- + -- Grab an optional exponential part + -- + local exponent_part = text:match("^[eE][-+]?%d+", i) or "" - i = i + exponent_part:len() + i = i + exponent_part:len() - local full_number_text = integer_part .. decimal_part .. exponent_part + local full_number_text = integer_part .. decimal_part .. exponent_part - if options.decodeNumbersAsObjects then - return OBJDEF:asNumber(full_number_text), i - end + if options.decodeNumbersAsObjects then + return OBJDEF:asNumber(full_number_text), i + end - -- - -- If we're told to stringify under certain conditions, so do. - -- We punt a bit when there's an exponent by just stringifying no matter what. - -- I suppose we should really look to see whether the exponent is actually big enough one - -- way or the other to trip stringification, but I'll be lazy about it until someone asks. - -- - if (options.decodeIntegerStringificationLength - and - (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0)) + -- + -- If we're told to stringify under certain conditions, so do. + -- We punt a bit when there's an exponent by just stringifying no matter what. + -- I suppose we should really look to see whether the exponent is actually big enough one + -- way or the other to trip stringification, but I'll be lazy about it until someone asks. + -- + if + ( + options.decodeIntegerStringificationLength + and (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0) + ) + or ( + options.decodeDecimalStringificationLength + and (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0) + ) + then + return full_number_text, i -- this returns the exact string representation seen in the original JSON + end - or + local as_number = tonumber(full_number_text) - (options.decodeDecimalStringificationLength - and - (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0)) - then - return full_number_text, i -- this returns the exact string representation seen in the original JSON - end + if not as_number then + self:onDecodeError("bad number", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end - - - local as_number = tonumber(full_number_text) - - if not as_number then - self:onDecodeError("bad number", text, start, options.etc) - return nil, start -- in case the error method doesn't abort, return something sensible - end - - return as_number, i + return as_number, i end - local function grok_string(self, text, start, options) + if text:sub(start, start) ~= '"' then + self:onDecodeError("expected string's opening quote", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end - if text:sub(start,start) ~= '"' then - self:onDecodeError("expected string's opening quote", text, start, options.etc) - return nil, start -- in case the error method doesn't abort, return something sensible - end + local i = start + 1 -- +1 to bypass the initial quote + local text_len = text:len() + local VALUE = "" + while i <= text_len do + local c = text:sub(i, i) + if c == '"' then + return VALUE, i + 1 + end + if c ~= "\\" then + VALUE = VALUE .. c + i = i + 1 + elseif text:match("^\\b", i) then + VALUE = VALUE .. "\b" + i = i + 2 + elseif text:match("^\\f", i) then + VALUE = VALUE .. "\f" + i = i + 2 + elseif text:match("^\\n", i) then + VALUE = VALUE .. "\n" + i = i + 2 + elseif text:match("^\\r", i) then + VALUE = VALUE .. "\r" + i = i + 2 + elseif text:match("^\\t", i) then + VALUE = VALUE .. "\t" + i = i + 2 + else + local hex = text:match( + "^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])", + i + ) + if hex then + i = i + 6 -- bypass what we just read - local i = start + 1 -- +1 to bypass the initial quote - local text_len = text:len() - local VALUE = "" - while i <= text_len do - local c = text:sub(i,i) - if c == '"' then - return VALUE, i + 1 - end - if c ~= '\\' then - VALUE = VALUE .. c - i = i + 1 - elseif text:match('^\\b', i) then - VALUE = VALUE .. "\b" - i = i + 2 - elseif text:match('^\\f', i) then - VALUE = VALUE .. "\f" - i = i + 2 - elseif text:match('^\\n', i) then - VALUE = VALUE .. "\n" - i = i + 2 - elseif text:match('^\\r', i) then - VALUE = VALUE .. "\r" - i = i + 2 - elseif text:match('^\\t', i) then - VALUE = VALUE .. "\t" - i = i + 2 - else - local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) - if hex then - i = i + 6 -- bypass what we just read + -- We have a Unicode codepoint. It could be standalone, or if in the proper range and + -- followed by another in a specific range, it'll be a two-code surrogate pair. + local codepoint = tonumber(hex, 16) + if codepoint >= 0xD800 and codepoint <= 0xDBFF then + -- it's a hi surrogate... see whether we have a following low + local lo_surrogate = + text:match("^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])", i) + if lo_surrogate then + i = i + 6 -- bypass the low surrogate we just read + codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) + else + -- not a proper low, so we'll just leave the first codepoint as is and spit it out. + end + end + VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) + else + -- just pass through what's escaped + VALUE = VALUE .. text:match("^\\(.)", i) + i = i + 2 + end + end + end - -- We have a Unicode codepoint. It could be standalone, or if in the proper range and - -- followed by another in a specific range, it'll be a two-code surrogate pair. - local codepoint = tonumber(hex, 16) - if codepoint >= 0xD800 and codepoint <= 0xDBFF then - -- it's a hi surrogate... see whether we have a following low - local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) - if lo_surrogate then - i = i + 6 -- bypass the low surrogate we just read - codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) - else - -- not a proper low, so we'll just leave the first codepoint as is and spit it out. - end - end - VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) - - else - - -- just pass through what's escaped - VALUE = VALUE .. text:match('^\\(.)', i) - i = i + 2 - end - end - end - - self:onDecodeError("unclosed string", text, start, options.etc) - return nil, start -- in case the error method doesn't abort, return something sensible + self:onDecodeError("unclosed string", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible end local function skip_whitespace(text, start) - local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 - if match_end then - return match_end + 1 - else - return start - end + local _, match_end = text:find("^[ \n\r\t]+", start) -- [http://www.ietf.org/rfc/rfc4627.txt] Section 2 + if match_end then + return match_end + 1 + else + return start + end end -- Count newlines in `text` up to `start` local function count_newlines(text, start) - local _, count = text:sub(1, start):gsub('\n', '\n') + local _, count = text:sub(1, start):gsub("\n", "\n") - return count + return count end -- Count characters from `i` to previous newline -- {hello:sa\nmple\n test} local function get_relative_i(text, i) - -- Find next newline from `i` backwards + -- Find next newline from `i` backwards - local count = 0 + local count = 0 - for j = i, 1, -1 do - if text:sub(j, j) == '\n' then - break - end - count = count + 1 - end + for j = i, 1, -1 do + if text:sub(j, j) == "\n" then + break + end + count = count + 1 + end - return count + return count end local grok_one -- assigned later local function grok_object(self, text, start, options) + if text:sub(start, start) ~= "{" then + self:onDecodeError("expected '{'", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end - if text:sub(start,start) ~= '{' then - self:onDecodeError("expected '{'", text, start, options.etc) - return nil, start -- in case the error method doesn't abort, return something sensible - end + local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' - local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' + local VALUE = self.strictTypes and self:newObject({}) or {} - local VALUE = self.strictTypes and self:newObject { } or { } + if text:sub(i, i) == "}" then + return VALUE, i + 1 + end + local text_len = text:len() + while i <= text_len do + local key, new_i = grok_string(self, text, i, options) - if text:sub(i,i) == '}' then - return VALUE, i + 1 - end - local text_len = text:len() - while i <= text_len do - local key, new_i = grok_string(self, text, i, options) + ---- Find start of JSON key + local key_start = i + local newlines = count_newlines(text, key_start) + local relative_start = get_relative_i(text, key_start) - ---- Find start of JSON key - local key_start = i - local newlines = count_newlines(text, key_start) - local relative_start = get_relative_i(text, key_start) + i = skip_whitespace(text, new_i) - i = skip_whitespace(text, new_i) + if text:sub(i, i) ~= ":" then + self:onDecodeError("expected colon", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end - if text:sub(i, i) ~= ':' then - self:onDecodeError("expected colon", text, i, options.etc) - return nil, i -- in case the error method doesn't abort, return something sensible - end + i = skip_whitespace(text, i + 1) - i = skip_whitespace(text, i + 1) + local new_val, new_i = grok_one(self, text, i, options) - local new_val, new_i = grok_one(self, text, i, options) + ---- Add start position so we can quickly jump to it + VALUE[key] = { + value = new_val, + line_number = (newlines or 0) + 1, + key_start = relative_start + 1, + value_start = get_relative_i(text, i), + } - ---- Add start position so we can quickly jump to it - VALUE[key] = { - value = new_val, - line_number = (newlines or 0) + 1, - key_start = relative_start + 1, - value_start = get_relative_i(text, i), - } + -- + -- Expect now either '}' to end things, or a ',' to allow us to continue. + -- + i = skip_whitespace(text, new_i) - -- - -- Expect now either '}' to end things, or a ',' to allow us to continue. - -- - i = skip_whitespace(text, new_i) + local c = text:sub(i, i) - local c = text:sub(i,i) + if c == "}" then + return VALUE, i + 1 + end - if c == '}' then - return VALUE, i + 1 - end + if text:sub(i, i) ~= "," then + self:onDecodeError("expected comma or '}'", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end - if text:sub(i, i) ~= ',' then - self:onDecodeError("expected comma or '}'", text, i, options.etc) - return nil, i -- in case the error method doesn't abort, return something sensible - end + i = skip_whitespace(text, i + 1) + end - i = skip_whitespace(text, i + 1) - end - - self:onDecodeError("unclosed '{'", text, start, options.etc) - return nil, start -- in case the error method doesn't abort, return something sensible + self:onDecodeError("unclosed '{'", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible end local function grok_array(self, text, start, options) - if text:sub(start,start) ~= '[' then - self:onDecodeError("expected '['", text, start, options.etc) - return nil, start -- in case the error method doesn't abort, return something sensible - end + if text:sub(start, start) ~= "[" then + self:onDecodeError("expected '['", text, start, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end - local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' - local VALUE = self.strictTypes and self:newArray { } or { } - if text:sub(i,i) == ']' then - return VALUE, i + 1 - end + local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' + local VALUE = self.strictTypes and self:newArray({}) or {} + if text:sub(i, i) == "]" then + return VALUE, i + 1 + end - local VALUE_INDEX = 1 + local VALUE_INDEX = 1 - local text_len = text:len() - while i <= text_len do - local val, new_i = grok_one(self, text, i, options) - local newlines = count_newlines(text, i) - local relative_start = get_relative_i(text, i) + local text_len = text:len() + while i <= text_len do + local val, new_i = grok_one(self, text, i, options) + local newlines = count_newlines(text, i) + local relative_start = get_relative_i(text, i) - i = skip_whitespace(text, new_i) + i = skip_whitespace(text, new_i) - -- can't table.insert(VALUE, val) here because it's a no-op if val is nil - VALUE[VALUE_INDEX] = { - value = val, - line_number = (newlines or 0) + 1, - value_start = relative_start, - key_start = relative_start, - } - VALUE_INDEX = VALUE_INDEX + 1 + -- can't table.insert(VALUE, val) here because it's a no-op if val is nil + VALUE[VALUE_INDEX] = { + value = val, + line_number = (newlines or 0) + 1, + value_start = relative_start, + key_start = relative_start, + } + VALUE_INDEX = VALUE_INDEX + 1 - -- - -- Expect now either ']' to end things, or a ',' to allow us to continue. - -- - local c = text:sub(i,i) - if c == ']' then - return VALUE, i + 1 - end - if text:sub(i, i) ~= ',' then - self:onDecodeError("expected comma or ']'", text, i, options.etc) - return nil, i -- in case the error method doesn't abort, return something sensible - end - i = skip_whitespace(text, i + 1) - end - self:onDecodeError("unclosed '['", text, start, options.etc) - return nil, i -- in case the error method doesn't abort, return something sensible + -- + -- Expect now either ']' to end things, or a ',' to allow us to continue. + -- + local c = text:sub(i, i) + if c == "]" then + return VALUE, i + 1 + end + if text:sub(i, i) ~= "," then + self:onDecodeError("expected comma or ']'", text, i, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible + end + i = skip_whitespace(text, i + 1) + end + self:onDecodeError("unclosed '['", text, start, options.etc) + return nil, i -- in case the error method doesn't abort, return something sensible end - grok_one = function(self, text, start, options) - -- Skip any whitespace - start = skip_whitespace(text, start) + -- Skip any whitespace + start = skip_whitespace(text, start) - if start > text:len() then - self:onDecodeError("unexpected end of string", text, nil, options.etc) - return nil, start -- in case the error method doesn't abort, return something sensible - end + if start > text:len() then + self:onDecodeError("unexpected end of string", text, nil, options.etc) + return nil, start -- in case the error method doesn't abort, return something sensible + end - if text:find('^"', start) then - return grok_string(self, text, start, options.etc) - - elseif text:find('^[-0123456789 ]', start) then - return grok_number(self, text, start, options) - - elseif text:find('^%{', start) then - return grok_object(self, text, start, options) - - elseif text:find('^%[', start) then - return grok_array(self, text, start, options) - - elseif text:find('^true', start) then - return true, start + 4 - - elseif text:find('^false', start) then - return false, start + 5 - - elseif text:find('^null', start) then - return nil, start + 4 - - else - self:onDecodeError("can't parse JSON", text, start, options.etc) - return nil, 1 -- in case the error method doesn't abort, return something sensible - end + if text:find('^"', start) then + return grok_string(self, text, start, options.etc) + elseif text:find("^[-0123456789 ]", start) then + return grok_number(self, text, start, options) + elseif text:find("^%{", start) then + return grok_object(self, text, start, options) + elseif text:find("^%[", start) then + return grok_array(self, text, start, options) + elseif text:find("^true", start) then + return true, start + 4 + elseif text:find("^false", start) then + return false, start + 5 + elseif text:find("^null", start) then + return nil, start + 4 + else + self:onDecodeError("can't parse JSON", text, start, options.etc) + return nil, 1 -- in case the error method doesn't abort, return something sensible + end end function OBJDEF:decode(text, etc, options) - -- - -- If the user didn't pass in a table of decode options, make an empty one. - -- - if type(options) ~= 'table' then - options = {} - end + -- + -- If the user didn't pass in a table of decode options, make an empty one. + -- + if type(options) ~= "table" then + options = {} + end - -- - -- If they passed in an 'etc' argument, stuff it into the options. - -- (If not, any 'etc' field in the options they passed in remains to be used) - -- - if etc ~= nil then - options.etc = etc - end + -- + -- If they passed in an 'etc' argument, stuff it into the options. + -- (If not, any 'etc' field in the options they passed in remains to be used) + -- + if etc ~= nil then + options.etc = etc + end - if text:match('^%s*$') then - -- an empty string is nothing, but not an error - return nil - end + if text:match("^%s*$") then + -- an empty string is nothing, but not an error + return nil + end - if text:match('^%s*<') then - -- Can't be JSON... we'll assume it's HTML - local error_message = "HTML passed to JSON:decode()" - self:onDecodeOfHTMLError(error_message, text, nil, options.etc) - return nil, error_message -- in case the error method doesn't abort, return something sensible - end + if text:match("^%s*<") then + -- Can't be JSON... we'll assume it's HTML + local error_message = "HTML passed to JSON:decode()" + self:onDecodeOfHTMLError(error_message, text, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end - -- - -- Ensure that it's not UTF-32 or UTF-16. - -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), - -- but this package can't handle them. - -- - if text:sub(1,1):byte() == 0 or (text:len() >= 2 and text:sub(2,2):byte() == 0) then - local error_message = "JSON package groks only UTF-8, sorry" - self:onDecodeError(error_message, text, nil, options.etc) - return nil, error_message -- in case the error method doesn't abort, return something sensible - end + -- + -- Ensure that it's not UTF-32 or UTF-16. + -- Those are perfectly valid encodings for JSON (as per RFC 4627 section 3), + -- but this package can't handle them. + -- + if text:sub(1, 1):byte() == 0 or (text:len() >= 2 and text:sub(2, 2):byte() == 0) then + local error_message = "JSON package groks only UTF-8, sorry" + self:onDecodeError(error_message, text, nil, options.etc) + return nil, error_message -- in case the error method doesn't abort, return something sensible + end - -- - -- apply global options - -- - if options.decodeNumbersAsObjects == nil then - options.decodeNumbersAsObjects = self.decodeNumbersAsObjects - end - if options.decodeIntegerStringificationLength == nil then - options.decodeIntegerStringificationLength = self.decodeIntegerStringificationLength - end - if options.decodeDecimalStringificationLength == nil then - options.decodeDecimalStringificationLength = self.decodeDecimalStringificationLength - end + -- + -- apply global options + -- + if options.decodeNumbersAsObjects == nil then + options.decodeNumbersAsObjects = self.decodeNumbersAsObjects + end + if options.decodeIntegerStringificationLength == nil then + options.decodeIntegerStringificationLength = self.decodeIntegerStringificationLength + end + if options.decodeDecimalStringificationLength == nil then + options.decodeDecimalStringificationLength = self.decodeDecimalStringificationLength + end - -- - -- Finally, go parse it - -- - local success, value, next_i = pcall(grok_one, self, text, 1, options) + -- + -- Finally, go parse it + -- + local success, value, next_i = pcall(grok_one, self, text, 1, options) - if success then + if success then + local error_message = nil + if next_i ~= #text + 1 then + -- something's left over after we parsed the first thing.... whitespace is allowed. + next_i = skip_whitespace(text, next_i) - local error_message = nil - if next_i ~= #text + 1 then - -- something's left over after we parsed the first thing.... whitespace is allowed. - next_i = skip_whitespace(text, next_i) - - -- if we have something left over now, it's trailing garbage - if next_i ~= #text + 1 then - value, error_message = self:onTrailingGarbage(text, next_i, value, options.etc) - end - end - return value, error_message - - else - - -- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received - -- the error message here as "value", so pass it along as an assert. - local error_message = value - if self.assert then - self.assert(false, error_message) - else - assert(false, error_message) - end - -- ...and if we're still here (because the assert didn't throw an error), - -- return a nil and throw the error message on as a second arg - return nil, error_message - - end + -- if we have something left over now, it's trailing garbage + if next_i ~= #text + 1 then + value, error_message = self:onTrailingGarbage(text, next_i, value, options.etc) + end + end + return value, error_message + else + -- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received + -- the error message here as "value", so pass it along as an assert. + local error_message = value + if self.assert then + self.assert(false, error_message) + else + assert(false, error_message) + end + -- ...and if we're still here (because the assert didn't throw an error), + -- return a nil and throw the error message on as a second arg + return nil, error_message + end end function OBJDEF.__tostring() - return "JSON encode/decode package" + return "JSON encode/decode package" end OBJDEF.__index = OBJDEF function OBJDEF:new(args) - local new = { } + local new = {} - if args then - for key, val in pairs(args) do - new[key] = val - end - end + if args then + for key, val in pairs(args) do + new[key] = val + end + end - return setmetatable(new, OBJDEF) + return setmetatable(new, OBJDEF) end return OBJDEF:new() - - diff --git a/lua/jsonfly/parsers.lua b/lua/jsonfly/parsers.lua index 9536ff8..3908111 100644 --- a/lua/jsonfly/parsers.lua +++ b/lua/jsonfly/parsers.lua @@ -15,121 +15,120 @@ ---@field key_start number local PRIMITIVE_TYPES = { - string = true, - number = true, - boolean = true, + string = true, + number = true, + boolean = true, } local CONTAINS_CHILDREN_TYPES = { - [2] = true, -- Module / Javascript Object - [8] = true, -- Field - [18] = true, -- Array - [19] = true, -- Object + [2] = true, -- Module / Javascript Object + [8] = true, -- Field + [18] = true, -- Array + [19] = true, -- Object } local M = {} ---@param entry JSONEntry local function get_contents_from_json_value(entry) - local value = entry.value + local value = entry.value - if type(value) == "table" then - -- Recursively get the contents of the table - local contents = {} + if type(value) == "table" then + -- Recursively get the contents of the table + local contents = {} - for k, v in pairs(value) do - contents[k] = get_contents_from_json_value(v) - end + for k, v in pairs(value) do + contents[k] = get_contents_from_json_value(v) + end - return contents - else - return entry.value - end + return contents + else + return entry.value + end end ---@param t table|nil|string|number|boolean ---@return Entry[] function M:get_entries_from_lua_json(t) - if PRIMITIVE_TYPES[type(t)] or t == nil then - return {} - end + if PRIMITIVE_TYPES[type(t)] or t == nil then + return {} + end - local keys = {} + local keys = {} - for k, _raw_value in pairs(t) do - ---@type JSONEntry - local raw_value = _raw_value - ---@type Entry - local entry = { - key = tostring(k), - value = get_contents_from_json_value(raw_value), - position = { - line_number = raw_value.line_number, - key_start = raw_value.key_start, - value_start = raw_value.value_start, - } - } - table.insert(keys, entry) + for k, _raw_value in pairs(t) do + ---@type JSONEntry + local raw_value = _raw_value + ---@type Entry + local entry = { + key = tostring(k), + value = get_contents_from_json_value(raw_value), + position = { + line_number = raw_value.line_number, + key_start = raw_value.key_start, + value_start = raw_value.value_start, + }, + } + table.insert(keys, entry) - local v = raw_value.value + local v = raw_value.value - if type(v) == "table" then - local sub_keys = M:get_entries_from_lua_json(v) + if type(v) == "table" then + local sub_keys = M:get_entries_from_lua_json(v) - for index=1, #sub_keys do - local sub_key = sub_keys[index] + for index = 1, #sub_keys do + local sub_key = sub_keys[index] - ---@type Entry - local entry = { - key = k .. "." .. sub_key.key, - value = sub_key.value, - position = sub_key.position, - } + ---@type Entry + local entry = { + key = k .. "." .. sub_key.key, + value = sub_key.value, + position = sub_key.position, + } - keys[#keys + 1] = entry - end - end - end + keys[#keys + 1] = entry + end + end + end - return keys + return keys end ---@param result Symbol ---@return string|number|table|boolean|nil function M:parse_lsp_value(result) - -- Object - if CONTAINS_CHILDREN_TYPES[result.kind] then - local value = {} + -- Object + if CONTAINS_CHILDREN_TYPES[result.kind] then + local value = {} - for _, child in ipairs(result.children) do - value[child.name] = M:parse_lsp_value(child) - end + for _, child in ipairs(result.children) do + value[child.name] = M:parse_lsp_value(child) + end - return value - -- Integer - elseif result.kind == 16 then - return tonumber(result.detail) - -- String - elseif result.kind == 15 then - return result.detail - -- Array - elseif result.kind == 18 then - local value = {} + return value + -- Integer + elseif result.kind == 16 then + return tonumber(result.detail) + -- String + elseif result.kind == 15 then + return result.detail + -- Array + elseif result.kind == 18 then + local value = {} - for i, child in ipairs(result.children) do - value[i] = M:parse_lsp_value(child) - end + for i, child in ipairs(result.children) do + value[i] = M:parse_lsp_value(child) + end - return value - -- null - elseif result.kind == 13 then - return nil - -- boolean - elseif result.kind == 17 then - return result.detail == "true" - end + return value + -- null + elseif result.kind == 13 then + return nil + -- boolean + elseif result.kind == 17 then + return result.detail == "true" + end end - ---@class Symbol ---@field name string ---@field kind number 2 = Object, 16 = Number, 15 = String, 18 = Array, 13 = Null, 17 = Boolean @@ -149,45 +148,45 @@ end ---@param symbols Symbol[] ---@return Entry[] function M:get_entries_from_lsp_symbols(symbols) - local keys = {} + local keys = {} - for index=1, #symbols do - local symbol = symbols[index] - local key = symbol.name + for index = 1, #symbols do + local symbol = symbols[index] + local key = symbol.name - ---@type Entry - local entry = { - key = tostring(key), - value = M:parse_lsp_value(symbol), - position = { - line_number = symbol.range.start.line + 1, - key_start = symbol.range.start.character + 2, - -- The LSP doesn't return the start of the value, so we'll just assume it's 3 characters after the key - -- We assume a default JSON file like: - -- `"my_key": "my_value"` - -- Since we get the end of the key, we can just add 4 to get the start of the value - value_start = symbol.selectionRange["end"].character + 3, - } - } - keys[#keys + 1] = entry + ---@type Entry + local entry = { + key = tostring(key), + value = M:parse_lsp_value(symbol), + position = { + line_number = symbol.range.start.line + 1, + key_start = symbol.range.start.character + 2, + -- The LSP doesn't return the start of the value, so we'll just assume it's 3 characters after the key + -- We assume a default JSON file like: + -- `"my_key": "my_value"` + -- Since we get the end of the key, we can just add 4 to get the start of the value + value_start = symbol.selectionRange["end"].character + 3, + }, + } + keys[#keys + 1] = entry - if CONTAINS_CHILDREN_TYPES[symbol.kind] then - local sub_keys = M:get_entries_from_lsp_symbols(symbol.children) + if CONTAINS_CHILDREN_TYPES[symbol.kind] then + local sub_keys = M:get_entries_from_lsp_symbols(symbol.children) - for jindex=1, #sub_keys do - ---@type Entry - local entry = { - key = key .. "." .. sub_keys[jindex].key, - value = sub_keys[jindex].value, - position = sub_keys[jindex].position, - } + for jindex = 1, #sub_keys do + ---@type Entry + local entry = { + key = key .. "." .. sub_keys[jindex].key, + value = sub_keys[jindex].value, + position = sub_keys[jindex].position, + } - keys[#keys + 1] = entry - end - end - end + keys[#keys + 1] = entry + end + end + end - return keys + return keys end return M diff --git a/lua/jsonfly/utils.lua b/lua/jsonfly/utils.lua index 5256498..3085a01 100644 --- a/lua/jsonfly/utils.lua +++ b/lua/jsonfly/utils.lua @@ -54,43 +54,43 @@ local M = {} function M:truncate_overflow(value, max_length, overflow_marker) - if vim.fn.strdisplaywidth(value) > max_length then - return value:sub(1, max_length - vim.fn.strdisplaywidth(overflow_marker)) .. overflow_marker - end + if vim.fn.strdisplaywidth(value) > max_length then + return value:sub(1, max_length - vim.fn.strdisplaywidth(overflow_marker)) .. overflow_marker + end - return value + return value end ---@param value any ---@param conceal boolean ---@param render_objects boolean function M:create_display_preview(value, conceal, render_objects) - local t = type(value) + local t = type(value) - if t == "table" then - if render_objects == false then - return "", "other" - end - local preview_table = {} + if t == "table" then + if render_objects == false then + return "", "other" + end + local preview_table = {} - for k, v in pairs(value) do - preview_table[#preview_table + 1] = k .. ": " .. M:create_display_preview(v, conceal, render_objects) - end + for k, v in pairs(value) do + preview_table[#preview_table + 1] = k .. ": " .. M:create_display_preview(v, conceal, render_objects) + end - return "{ " .. table.concat(preview_table, ", ") .. " }", "other" - elseif t == "nil" then - return "null", "null" - elseif t == "number" then - return tostring(value), "number" - elseif t == "string" then - if conceal then - return value, "string" - else - return "\"" .. value .. "\"", "string" - end - elseif t == "boolean" then - return value and "true" or "false", "boolean" - end + return "{ " .. table.concat(preview_table, ", ") .. " }", "other" + elseif t == "nil" then + return "null", "null" + elseif t == "number" then + return tostring(value), "number" + elseif t == "string" then + if conceal then + return value, "string" + else + return '"' .. value .. '"', "string" + end + elseif t == "boolean" then + return value and "true" or "false", "boolean" + end end ---@param key string @@ -99,110 +99,110 @@ end ---Replaces all previous keys with the replacement ---Example: replace_previous_keys("a.b.c", "x") => "xxx.c" function M:replace_previous_keys(key, replacement) - for i = #key, 1, -1 do - if key:sub(i, i) == "." then - local len = i - 1 - local before = replacement:rep(len) + for i = #key, 1, -1 do + if key:sub(i, i) == "." then + local len = i - 1 + local before = replacement:rep(len) - return before .. "." .. key:sub(i + 1) - end - end + return before .. "." .. key:sub(i + 1) + end + end - return key + return key end ---@param text string ---@param char string ---@return string[] function M:split_by_char(text, char) - local parts = {} - local current = "" + local parts = {} + local current = "" - for i = 1, #text do - local c = text:sub(i, i) + for i = 1, #text do + local c = text:sub(i, i) - if c == char then - parts[#parts + 1] = current - current = "" - else - current = current .. c - end - end + if c == char then + parts[#parts + 1] = current + current = "" + else + current = current .. c + end + end - parts[#parts + 1] = current + parts[#parts + 1] = current - return parts + return parts end ---@param text string ---@return KeyDescription[] function M:extract_key_description(text) - local keys = {} + local keys = {} - local splitted = M:split_by_char(text, ".") + local splitted = M:split_by_char(text, ".") - local index = 1 + local index = 1 - while index <= #splitted do - local token = splitted[index] + while index <= #splitted do + local token = splitted[index] - -- Escape - if string.sub(token, 1, 1) == "\\" then - token = token:sub(2) + -- Escape + if string.sub(token, 1, 1) == "\\" then + token = token:sub(2) - keys[#keys + 1] = { - type = "object_wrapper", - } - keys[#keys + 1] = { - key = token, - type = "key", - } - -- Array - elseif string.match(token, "%[%d+%]") then - local array_index = tonumber(string.sub(token, 2, -2)) + keys[#keys + 1] = { + type = "object_wrapper", + } + keys[#keys + 1] = { + key = token, + type = "key", + } + -- Array + elseif string.match(token, "%[%d+%]") then + local array_index = tonumber(string.sub(token, 2, -2)) - keys[#keys + 1] = { - type = "array_wrapper", - } - keys[#keys + 1] = { - key = array_index, - type = "array_index", - } - -- Array - elseif string.match(token, "%d+") then - local array_index = tonumber(token) + keys[#keys + 1] = { + type = "array_wrapper", + } + keys[#keys + 1] = { + key = array_index, + type = "array_index", + } + -- Array + elseif string.match(token, "%d+") then + local array_index = tonumber(token) - keys[#keys + 1] = { - type = "array_wrapper", - } - keys[#keys + 1] = { - key = array_index, - type = "array_index", - } - -- Object - else - keys[#keys + 1] = { - type = "object_wrapper", - } - keys[#keys + 1] = { - key = token, - type = "key", - } - end + keys[#keys + 1] = { + type = "array_wrapper", + } + keys[#keys + 1] = { + key = array_index, + type = "array_index", + } + -- Object + else + keys[#keys + 1] = { + type = "object_wrapper", + } + keys[#keys + 1] = { + key = token, + type = "key", + } + end - index = index + 1 - end + index = index + 1 + end - if #keys == 0 then - return { - { - key = text, - type = "key", - } - } - end + if #keys == 0 then + return { + { + key = text, + type = "key", + }, + } + end - return keys + return keys end return M diff --git a/lua/telescope/_extensions/jsonfly.lua b/lua/telescope/_extensions/jsonfly.lua index 2ac4327..2dbf430 100644 --- a/lua/telescope/_extensions/jsonfly.lua +++ b/lua/telescope/_extensions/jsonfly.lua @@ -25,43 +25,43 @@ ---@field null string - Highlight group for null values, Default: "@constant.builtin.json" ---@field other string - Highlight group for other types, Default: "@label.json" -local parsers = require"jsonfly.parsers" -local utils = require"jsonfly.utils" -local cache = require"jsonfly.cache" -local insert = require"jsonfly.insert" +local parsers = require("jsonfly.parsers") +local utils = require("jsonfly.utils") +local cache = require("jsonfly.cache") +local insert = require("jsonfly.insert") -local json = require"jsonfly.json" -local finders = require "telescope.finders" -local pickers = require "telescope.pickers" +local json = require("jsonfly.json") +local finders = require("telescope.finders") +local pickers = require("telescope.pickers") local conf = require("telescope.config").values -local make_entry = require "telescope.make_entry" -local entry_display = require "telescope.pickers.entry_display" +local make_entry = require("telescope.make_entry") +local entry_display = require("telescope.pickers.entry_display") -local action_state = require "telescope.actions.state" +local action_state = require("telescope.actions.state") ---@type Options local DEFAULT_CONFIG = { - key_max_length = 50, - key_exact_length = false, - max_length = 9999, - overflow_marker = "…", - conceal = "auto", - prompt_title = "JSON(fly)", - highlights = { - string = "@string.json", - number = "@number.json", - boolean = "@boolean.json", - null = "@constant.builtin.json", - other = "@label.json", - }, - jump_behavior = "key_start", - subkeys_display = "normal", - show_nested_child_preview = true, - backend = "lsp", - use_cache = 500, - commands = { - add_key = {"i", ""} - } + key_max_length = 50, + key_exact_length = false, + max_length = 9999, + overflow_marker = "…", + conceal = "auto", + prompt_title = "JSON(fly)", + highlights = { + string = "@string.json", + number = "@number.json", + boolean = "@boolean.json", + null = "@constant.builtin.json", + other = "@label.json", + }, + jump_behavior = "key_start", + subkeys_display = "normal", + show_nested_child_preview = true, + backend = "lsp", + use_cache = 500, + commands = { + add_key = { "i", "" }, + }, } local global_config = {} @@ -69,168 +69,179 @@ local global_config = {} ---@param entries Entry[] ---@param buffer number local function show_picker(entries, buffer, xopts) - local config = vim.tbl_deep_extend("force", global_config, xopts or {}) - local filename = vim.api.nvim_buf_get_name(buffer) + local config = vim.tbl_deep_extend("force", global_config, xopts or {}) + local filename = vim.api.nvim_buf_get_name(buffer) - local displayer = entry_display.create { - separator = " ", - items = { - { width = 1 }, - global_config.key_exact_length and { width = global_config.key_max_length } or { remaining = true }, - { remaining = true }, - }, - } - ---@type boolean - local conceal + local displayer = entry_display.create({ + separator = " ", + items = { + { width = 1 }, + global_config.key_exact_length and { width = global_config.key_max_length } or { remaining = true }, + { remaining = true }, + }, + }) + ---@type boolean + local conceal - if global_config.conceal == "auto" then - conceal = vim.o.conceallevel > 0 - else - conceal = global_config.conceal == true - end + if global_config.conceal == "auto" then + conceal = vim.o.conceallevel > 0 + else + conceal = global_config.conceal == true + end - pickers.new(config, { - prompt_title = global_config.prompt_title, - attach_mappings = function(_, map) - map( - global_config.commands.add_key[1], - global_config.commands.add_key[2], - function(prompt_bufnr) - local current_picker = action_state.get_current_picker(prompt_bufnr) - local input = current_picker:_get_prompt() + pickers + .new(config, { + prompt_title = global_config.prompt_title, + attach_mappings = function(_, map) + map(global_config.commands.add_key[1], global_config.commands.add_key[2], function(prompt_bufnr) + local current_picker = action_state.get_current_picker(prompt_bufnr) + local input = current_picker:_get_prompt() - local key_descriptor = utils:extract_key_description(input) + local key_descriptor = utils:extract_key_description(input) - insert:insert_new_key(entries, key_descriptor, buffer) - end - ) + insert:insert_new_key(entries, key_descriptor, buffer) + end) - return true - end, - finder = finders.new_table { - results = entries, - ---@param entry Entry - entry_maker = function(entry) - local _, raw_depth = entry.key:gsub("%.", ".") - local depth = (raw_depth or 0) + 1 + return true + end, + finder = finders.new_table({ + results = entries, + ---@param entry Entry + entry_maker = function(entry) + local _, raw_depth = entry.key:gsub("%.", ".") + local depth = (raw_depth or 0) + 1 - return make_entry.set_default_entry_mt({ - value = buffer, - ordinal = entry.key, - display = function(_) - local preview, hl_group_key = utils:create_display_preview(entry.value, conceal, global_config.show_nested_child_preview) + return make_entry.set_default_entry_mt({ + value = buffer, + ordinal = entry.key, + display = function(_) + local preview, hl_group_key = utils:create_display_preview( + entry.value, + conceal, + global_config.show_nested_child_preview + ) - local key = global_config.subkeys_display == "normal" and entry.key or utils:replace_previous_keys(entry.key, " ") + local key = global_config.subkeys_display == "normal" and entry.key + or utils:replace_previous_keys(entry.key, " ") - return displayer { - { depth, "TelescopeResultsNumber"}, - { - utils:truncate_overflow( - key, - global_config.key_max_length, - global_config.overflow_marker - ), - "@property.json", - }, - { - utils:truncate_overflow( - preview, - global_config.max_length, - global_config.overflow_marker - ), - global_config.highlights[hl_group_key] or "TelescopeResultsString", - }, - } - end, + return displayer({ + { depth, "TelescopeResultsNumber" }, + { + utils:truncate_overflow( + key, + global_config.key_max_length, + global_config.overflow_marker + ), + "@property.json", + }, + { + utils:truncate_overflow( + preview, + global_config.max_length, + global_config.overflow_marker + ), + global_config.highlights[hl_group_key] or "TelescopeResultsString", + }, + }) + end, - bufnr = buffer, - filename = filename, - lnum = entry.position.line_number, - col = global_config.jump_behavior == "key_start" - and entry.position.key_start - -- Use length ("#" operator) as vim jumps to the bytes, not characters - or entry.position.value_start - }, config) - end, - }, - previewer = conf.grep_previewer(config), - sorter = conf.generic_sorter(config), - sorting_strategy = "ascending", - }):find() + bufnr = buffer, + filename = filename, + lnum = entry.position.line_number, + col = global_config.jump_behavior == "key_start" and entry.position.key_start + -- Use length ("#" operator) as vim jumps to the bytes, not characters + or entry.position.value_start, + }, config) + end, + }), + previewer = conf.grep_previewer(config), + sorter = conf.generic_sorter(config), + sorting_strategy = "ascending", + }) + :find() end -return require("telescope").register_extension { - setup = function(extension_config) - global_config = vim.tbl_deep_extend("force", DEFAULT_CONFIG, extension_config or {}) - end, - exports = { - jsonfly = function(xopts) - local current_buf = vim.api.nvim_get_current_buf() +return require("telescope").register_extension({ + setup = function(extension_config) + global_config = vim.tbl_deep_extend("force", DEFAULT_CONFIG, extension_config or {}) + end, + exports = { + jsonfly = function(xopts) + local current_buf = vim.api.nvim_get_current_buf() - local cached_entries = cache:get_cache(current_buf) + local cached_entries = cache:get_cache(current_buf) - if cached_entries ~= nil then - show_picker(cached_entries, current_buf, xopts) - return - end + if cached_entries ~= nil then + show_picker(cached_entries, current_buf, xopts) + return + end - local content_lines = vim.api.nvim_buf_get_lines(current_buf, 0, -1, false) - local content = table.concat(content_lines, "\n") - local allow_cache = global_config.use_cache > 0 and #content_lines >= global_config.use_cache + local content_lines = vim.api.nvim_buf_get_lines(current_buf, 0, -1, false) + local content = table.concat(content_lines, "\n") + local allow_cache = global_config.use_cache > 0 and #content_lines >= global_config.use_cache - if allow_cache then - cache:register_listeners(current_buf) - end + if allow_cache then + cache:register_listeners(current_buf) + end - local function run_lua_parser() - local parsed = json:decode(content) - local entries = parsers:get_entries_from_lua_json(parsed) + local function run_lua_parser() + local parsed = json:decode(content) + local entries = parsers:get_entries_from_lua_json(parsed) - if allow_cache then - cache:cache_buffer(current_buf, entries) - end + if allow_cache then + cache:cache_buffer(current_buf, entries) + end - show_picker(entries, current_buf, xopts) - end + show_picker(entries, current_buf, xopts) + end - if global_config.backend == "lsp" then - local params = vim.lsp.util.make_position_params(xopts.winnr) + if global_config.backend == "lsp" then + local params = vim.lsp.util.make_position_params(xopts.winnr) - vim.lsp.buf_request_all( - current_buf, - "textDocument/documentSymbol", - params, - function(results) - if results == nil or vim.tbl_isempty(results) then - run_lua_parser() - return - end + -- Check i + local clients = vim.lsp.get_clients() - local combined_result = {} + local any_support = false - for _, res in pairs(results) do - if res.result then - vim.list_extend(combined_result, res.result) - end - end + for _, client in ipairs(clients) do + if client.supports_method("textDocument/documentSymbol") then + any_support = true + break + end + end - if vim.tbl_isempty(combined_result) then - run_lua_parser() - return - end + if any_support then + vim.lsp.buf_request_all(current_buf, "textDocument/documentSymbol", params, function(results) + if results == nil or vim.tbl_isempty(results) then + run_lua_parser() + return + end - local entries = parsers:get_entries_from_lsp_symbols(combined_result) + local combined_result = {} - if allow_cache then - cache:cache_buffer(current_buf, entries) - end + for _, res in pairs(results) do + if res.result then + vim.list_extend(combined_result, res.result) + end + end - show_picker(entries, current_buf, xopts) - end - ) - else - run_lua_parser() - end - end - } -} + if vim.tbl_isempty(combined_result) then + run_lua_parser() + return + end + + local entries = parsers:get_entries_from_lsp_symbols(combined_result) + + if allow_cache then + cache:cache_buffer(current_buf, entries) + end + + show_picker(entries, current_buf, xopts) + end) + end + end + + run_lua_parser() + end, + }, +})