From cbff2a5659c61d18c42b9844b7f9406e2ebd7d0c Mon Sep 17 00:00:00 2001 From: Tobias Klein Date: Sun, 29 Sep 2024 15:55:36 +0200 Subject: [PATCH 1/2] fix(jsonfly.lua): resolve function buf_request_all to work with new nvim syntax and version changes not accounted for. Refactored LSP request handling in jsonfly.lua to combine results from multiple LSP responses and handle empty results more gracefully. Improved code readability and maintainability by restructuring the nested if-else blocks. --- lua/telescope/_extensions/jsonfly.lua | 51 ++++++++++++++++----------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/lua/telescope/_extensions/jsonfly.lua b/lua/telescope/_extensions/jsonfly.lua index 089484b..2ac4327 100644 --- a/lua/telescope/_extensions/jsonfly.lua +++ b/lua/telescope/_extensions/jsonfly.lua @@ -196,27 +196,38 @@ return require("telescope").register_extension { 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(response) - if response == nil or #response == 0 then - run_lua_parser() - return - end - - local result = response[1].result - - local entries = parsers:get_entries_from_lsp_symbols(result) - - if allow_cache then - cache:cache_buffer(current_buf, entries) - end - - show_picker(entries, current_buf, xopts) + 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 combined_result = {} + + for _, res in pairs(results) do + if res.result then + vim.list_extend(combined_result, res.result) + 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 + ) else run_lua_parser() end 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 2/2] 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, + }, +})