fix: merge main

This commit is contained in:
Myzel394 2025-06-06 12:43:58 +02:00
commit 895e9adfdf
Signed by: Myzel394
GPG Key ID: 79E65B4AA44EBBF0
6 changed files with 1116 additions and 1117 deletions

View File

@ -1,47 +1,42 @@
local M = {}; local M = {}
local _cache = {}; local _cache = {}
---@param buffer integer ---@param buffer integer
function M:cache_buffer(buffer, value) function M:cache_buffer(buffer, value)
_cache[buffer] = value; _cache[buffer] = value
end end
---@param buffer integer ---@param buffer integer
function M:invalidate_buffer(buffer) function M:invalidate_buffer(buffer)
_cache[buffer] = nil; _cache[buffer] = nil
end end
---@param buffer integer ---@param buffer integer
---@return string[]|nil ---@return string[]|nil
function M:get_cache(buffer) function M:get_cache(buffer)
return _cache[buffer]; return _cache[buffer]
end end
local _listening_buffers = {}; local _listening_buffers = {}
---@param buffer integer ---@param buffer integer
function M:register_listeners(buffer) function M:register_listeners(buffer)
if _listening_buffers[buffer] then if _listening_buffers[buffer] then
return; return
end end
_listening_buffers[buffer] = true; _listening_buffers[buffer] = true
vim.api.nvim_buf_attach( vim.api.nvim_buf_attach(buffer, false, {
buffer, on_lines = function()
false, self:invalidate_buffer(buffer)
{ end,
on_lines = function() on_detach = function()
self:invalidate_buffer(buffer) self:invalidate_buffer(buffer)
end, _listening_buffers[buffer] = nil
on_detach = function() end,
self:invalidate_buffer(buffer) })
_listening_buffers[buffer] = nil;
end,
}
);
end end
return M; return M

View File

@ -1,42 +1,42 @@
local utils = require"jsonfly.utils" local utils = require("jsonfly.utils")
-- This string will be used to position the cursor properly. -- This string will be used to position the cursor properly.
-- Once everything is set, the cursor searches for this string and jumps to it. -- Once everything is set, the cursor searches for this string and jumps to it.
-- After that, it will be removed immediately. -- After that, it will be removed immediately.
local CURSOR_SEARCH_HELPER = "_jsonFfFfFfLyY0904857CursorHelperRrRrRrR" local CURSOR_SEARCH_HELPER = "_jsonFfFfFfLyY0904857CursorHelperRrRrRrR"
local M = {}; local M = {}
-- https://stackoverflow.com/a/24823383/9878135 -- https://stackoverflow.com/a/24823383/9878135
function table.slice(tbl, first, last, step) function table.slice(tbl, first, last, step)
local sliced = {} local sliced = {}
for i = first or 1, last or #tbl, step or 1 do for i = first or 1, last or #tbl, step or 1 do
sliced[#sliced+1] = tbl[i] sliced[#sliced + 1] = tbl[i]
end end
return sliced return sliced
end end
---@param line string ---@param line string
---@param also_match_end_bracket boolean - Whether to also match only a closing bracket ---@param also_match_end_bracket boolean - Whether to also match only a closing bracket
---@return boolean - Whether the line contains an empty JSON object ---@return boolean - Whether the line contains an empty JSON object
local function line_contains_empty_json(line, also_match_end_bracket) local function line_contains_empty_json(line, also_match_end_bracket)
-- Starting and ending on same line -- Starting and ending on same line
return string.match(line, ".*[%{%[]%s*[%}%]]%s*,?*%s*") return string.match(line, ".*[%{%[]%s*[%}%]]%s*,?*%s*")
-- Opening bracket on line -- Opening bracket on line
or string.match(line, ".*[%{%[]%s*") or string.match(line, ".*[%{%[]%s*")
-- Closing bracket on line -- Closing bracket on line
or (also_match_end_bracket and string.match(line, ".*.*[%}%]]%s*,?%s*")) or (also_match_end_bracket and string.match(line, ".*.*[%}%]]%s*,?%s*"))
end end
---@param entry Entry ---@param entry Entry
---@param key string ---@param key string
---@param index number ---@param index number
local function check_key_equal(entry, key, index) 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 end
---Find the entry in `entries` with the most matching keys at the beginning based on the `keys`. ---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[] ---@param keys string[]
---@return number|nil ---@return number|nil
local function find_best_fitting_entry(entries, keys) local function find_best_fitting_entry(entries, keys)
local entry_index local entry_index
local current_indexes = {1, #entries} local current_indexes = { 1, #entries }
for kk=1, #keys do for kk = 1, #keys do
local key = keys[kk] local key = keys[kk]
local start_index = current_indexes[1] local start_index = current_indexes[1]
local end_index = current_indexes[2] local end_index = current_indexes[2]
current_indexes = {nil, nil} current_indexes = { nil, nil }
for ii=start_index, end_index do for ii = start_index, end_index do
if check_key_equal(entries[ii], key, kk) then if check_key_equal(entries[ii], key, kk) then
if current_indexes[1] == nil then if current_indexes[1] == nil then
current_indexes[1] = ii current_indexes[1] = ii
end end
current_indexes[2] = ii current_indexes[2] = ii
end end
end end
if current_indexes[1] == nil then if current_indexes[1] == nil then
-- No entries found -- No entries found
break break
else else
entry_index = current_indexes[1] entry_index = current_indexes[1]
end end
end end
return entry_index return entry_index
end end
---@param keys KeyDescription ---@param keys KeyDescription
---@param index number - Index of the key ---@param index number - Index of the key
---@param lines string[] - Table to write the lines to ---@param lines string[] - Table to write the lines to
local function write_keys(keys, index, lines) local function write_keys(keys, index, lines)
local key = keys[index] local key = keys[index]
if index == #keys then if index == #keys then
lines[#lines + 1] = "\"" .. key.key .. "\": \"" .. CURSOR_SEARCH_HELPER .. "\"" lines[#lines + 1] = '"' .. key.key .. '": "' .. CURSOR_SEARCH_HELPER .. '"'
return return
end end
if key.type == "object_wrapper" then if key.type == "object_wrapper" then
local previous_line = lines[#lines] or "" local previous_line = lines[#lines] or ""
if line_contains_empty_json(previous_line, true) or #lines == 0 then if line_contains_empty_json(previous_line, true) or #lines == 0 then
lines[#lines + 1] = "{" lines[#lines + 1] = "{"
else else
lines[#lines] = previous_line .. " {" lines[#lines] = previous_line .. " {"
end end
write_keys(keys, index + 1, lines) write_keys(keys, index + 1, lines)
lines[#lines + 1] = "}" lines[#lines + 1] = "}"
elseif key.type == "key" then elseif key.type == "key" then
lines[#lines + 1] = "\"" .. key.key .. "\":" lines[#lines + 1] = '"' .. key.key .. '":'
write_keys(keys, index + 1, lines) write_keys(keys, index + 1, lines)
elseif key.type == "array_wrapper" then elseif key.type == "array_wrapper" then
local previous_line = lines[#lines] or "" local previous_line = lines[#lines] or ""
-- Starting and ending on same line -- Starting and ending on same line
if line_contains_empty_json(previous_line, true) or #lines == 0 then if line_contains_empty_json(previous_line, true) or #lines == 0 then
lines[#lines + 1] = "[" lines[#lines + 1] = "["
else else
lines[#lines] = previous_line .. " [" lines[#lines] = previous_line .. " ["
end end
write_keys(keys, index + 1, lines) write_keys(keys, index + 1, lines)
lines[#lines + 1] = "]" lines[#lines + 1] = "]"
elseif key.type == "array_index" then elseif key.type == "array_index" then
local amount = tonumber(key.key) local amount = tonumber(key.key)
-- Write previous empty array objects -- Write previous empty array objects
for _=1, amount do for _ = 1, amount do
lines[#lines + 1] = "{}," lines[#lines + 1] = "{},"
end end
write_keys(keys, index + 1, lines) write_keys(keys, index + 1, lines)
end end
end end
---@param buffer number ---@param buffer number
---@param insertion_line number ---@param insertion_line number
local function add_comma(buffer, insertion_line) local function add_comma(buffer, insertion_line)
local BUFFER_SIZE = 5 local BUFFER_SIZE = 5
-- Find next non-empty character in reverse -- Find next non-empty character in reverse
for ii=insertion_line, 0, -BUFFER_SIZE do for ii = insertion_line, 0, -BUFFER_SIZE do
local previous_lines = vim.api.nvim_buf_get_lines( local previous_lines = vim.api.nvim_buf_get_lines(buffer, math.max(0, ii - BUFFER_SIZE), ii, false)
buffer,
math.max(0, ii - BUFFER_SIZE),
ii,
false
)
if #previous_lines == 0 then if #previous_lines == 0 then
return return
end end
for jj=#previous_lines, 1, -1 do for jj = #previous_lines, 1, -1 do
local line = previous_lines[jj] local line = previous_lines[jj]
for char_index=#line, 1, -1 do for char_index = #line, 1, -1 do
local char = line:sub(char_index, char_index) local char = line:sub(char_index, char_index)
if char ~= " " and char ~= "\t" and char ~= "\n" and char ~= "\r" then if char ~= " " and char ~= "\t" and char ~= "\n" and char ~= "\r" then
if char == "," or char == "{" or char == "[" then if char == "," or char == "{" or char == "[" then
return return
end end
-- Insert comma at position -- Insert comma at position
local line_number = math.max(0, ii - BUFFER_SIZE) + jj - 1 local line_number = math.max(0, ii - BUFFER_SIZE) + jj - 1
vim.api.nvim_buf_set_text( vim.api.nvim_buf_set_text(buffer, line_number, char_index, line_number, char_index, { "," })
buffer, return
line_number, end
char_index, end
line_number, end
char_index, end
{","}
)
return
end
end
end
end
end end
---@return number - The new line number to be used, as the buffer has been modified ---@return number - The new line number to be used, as the buffer has been modified
local function expand_empty_object(buffer, line_number) 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 if line_contains_empty_json(line, false) then
local position_closing_bracket = string.find(line, "[%}%]]") local position_closing_bracket = string.find(line, "[%}%]]")
local remaining_line = string.sub(line, position_closing_bracket + 1) local remaining_line = string.sub(line, position_closing_bracket + 1)
vim.api.nvim_buf_set_lines( vim.api.nvim_buf_set_lines(buffer, line_number, line_number + 1, false, {
buffer, "{",
line_number, "}" .. remaining_line,
line_number + 1, })
false,
{
"{",
"}" .. remaining_line
}
)
return line_number + 1 return line_number + 1
end end
return line_number return line_number
end end
---@param keys KeyDescription[] ---@param keys KeyDescription[]
---@param input_key_depth number ---@param input_key_depth number
local function get_key_descriptor_index(keys, input_key_depth) local function get_key_descriptor_index(keys, input_key_depth)
local depth = 0 local depth = 0
local index = 0 local index = 0
for ii=1, #keys do for ii = 1, #keys do
if keys[ii].type == "key" or keys[ii].type == "array_index" then if keys[ii].type == "key" or keys[ii].type == "array_index" then
depth = depth + 1 depth = depth + 1
end end
if depth >= input_key_depth then if depth >= input_key_depth then
index = ii index = ii
break break
end end
end end
return index return index
end end
---@param entries Entry[] ---@param entries Entry[]
---@param keys string[] ---@param keys string[]
---@return integer|nil - The index of the entry ---@return integer|nil - The index of the entry
local function get_entry_by_keys(entries, keys) local function get_entry_by_keys(entries, keys)
for ii=1, #entries do for ii = 1, #entries do
local entry = entries[ii] local entry = entries[ii]
local splitted = utils:split_by_char(entry.key, ".") local splitted = utils:split_by_char(entry.key, ".")
local found = true local found = true
for jj=1, #keys do for jj = 1, #keys do
if keys[jj] ~= splitted[jj] then if keys[jj] ~= splitted[jj] then
found = false found = false
break break
end end
end end
if found then if found then
return ii return ii
end end
end end
end end
---@param keys KeyDescription[] ---@param keys KeyDescription[]
---@return string[] ---@return string[]
local function flatten_key_description(keys) local function flatten_key_description(keys)
local flat_keys = {} local flat_keys = {}
for ii=1, #keys do for ii = 1, #keys do
if keys[ii].type == "key" then if keys[ii].type == "key" then
flat_keys[#flat_keys + 1] = keys[ii].key flat_keys[#flat_keys + 1] = keys[ii].key
elseif keys[ii].type == "array_index" then elseif keys[ii].type == "array_index" then
flat_keys[#flat_keys + 1] = tostring(keys[ii].key) flat_keys[#flat_keys + 1] = tostring(keys[ii].key)
end end
end end
return flat_keys return flat_keys
end end
---Subtracts indexes if there are other indexes before already ---Subtracts indexes if there are other indexes before already
@ -262,43 +244,43 @@ end
---@param starting_keys KeyDescription[] ---@param starting_keys KeyDescription[]
---@param key KeyDescription - Th key to be inserted; must be of type `array_index`; will be modified in-place ---@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 function normalize_array_indexes(entries, starting_keys, key)
local starting_keys_flat = flatten_key_description(starting_keys) local starting_keys_flat = flatten_key_description(starting_keys)
local starting_key_index = get_entry_by_keys(entries, starting_keys_flat) local starting_key_index = get_entry_by_keys(entries, starting_keys_flat)
local entry = entries[starting_key_index] local entry = entries[starting_key_index]
key.key = key.key - #entry.value key.key = key.key - #entry.value
end end
---@param entries Entry[] - Entries, they must be children of a top level array ---@param entries Entry[] - Entries, they must be children of a top level array
---Counts how many top level children an array has ---Counts how many top level children an array has
local function count_array_children(entries) local function count_array_children(entries)
for ii=1, #entries do for ii = 1, #entries do
if string.match(entries[ii].key, "^%d+$") then if string.match(entries[ii].key, "^%d+$") then
return ii return ii
end end
end end
return #entries return #entries
end end
---Jump to the cursor helper and remove it ---Jump to the cursor helper and remove it
---@param buffer number ---@param buffer number
function M:jump_to_cursor_helper(buffer) function M:jump_to_cursor_helper(buffer)
vim.fn.search(CURSOR_SEARCH_HELPER) vim.fn.search(CURSOR_SEARCH_HELPER)
-- Remove cursor helper -- Remove cursor helper
local position = vim.api.nvim_win_get_cursor(0) local position = vim.api.nvim_win_get_cursor(0)
vim.api.nvim_buf_set_text( vim.api.nvim_buf_set_text(
buffer, buffer,
position[1] - 1, position[1] - 1,
position[2], position[2],
position[1] - 1, position[1] - 1,
position[2] + #CURSOR_SEARCH_HELPER, position[2] + #CURSOR_SEARCH_HELPER,
{""} { "" }
) )
-- -- Go into insert mode -- -- Go into insert mode
vim.cmd [[execute "normal a"]] vim.cmd([[execute "normal a"]])
end end
-- TODO: Handle top level empty arrays -- TODO: Handle top level empty arrays
@ -306,107 +288,107 @@ end
---@param keys KeyDescription[] ---@param keys KeyDescription[]
---@param buffer number ---@param buffer number
function M:insert_new_key(entries, keys, buffer) function M:insert_new_key(entries, keys, buffer)
-- Close current buffer -- Close current buffer
vim.cmd [[quit!]] vim.cmd([[quit!]])
local input_key = flatten_key_description(keys) local input_key = flatten_key_description(keys)
---@type boolean ---@type boolean
local should_add_comma = true local should_add_comma = true
---@type KeyDescription[] ---@type KeyDescription[]
local remaining_keys local remaining_keys
---@type Entry ---@type Entry
local entry local entry
if #entries == 0 then if #entries == 0 then
remaining_keys = table.slice(keys, 2, #keys) remaining_keys = table.slice(keys, 2, #keys)
entry = { entry = {
key = "", key = "",
position = { position = {
key_start = 1, key_start = 1,
line_number = 1, line_number = 1,
value_start = 1 value_start = 1,
} },
} }
should_add_comma = false should_add_comma = false
else else
local entry_index = find_best_fitting_entry(entries, input_key) or 0 local entry_index = find_best_fitting_entry(entries, input_key) or 0
entry = entries[entry_index] entry = entries[entry_index]
---@type integer ---@type integer
local existing_keys_index local existing_keys_index
if entry == nil then if entry == nil then
-- Insert as root -- Insert as root
existing_keys_index = 0 existing_keys_index = 0
remaining_keys = table.slice(keys, 2, #keys) remaining_keys = table.slice(keys, 2, #keys)
-- Top level array -- Top level array
if entries[1].key == "0" then if entries[1].key == "0" then
-- Normalize array indexes -- Normalize array indexes
remaining_keys[1].key = remaining_keys[1].key - count_array_children(entries) remaining_keys[1].key = remaining_keys[1].key - count_array_children(entries)
end end
entry = { entry = {
key = "", key = "",
position = { position = {
key_start = 1, key_start = 1,
line_number = 1, line_number = 1,
value_start = 1 value_start = 1,
} },
} }
else else
local existing_input_keys_depth = #utils:split_by_char(entry.key, ".") + 1 local existing_input_keys_depth = #utils:split_by_char(entry.key, ".") + 1
existing_keys_index = get_key_descriptor_index(keys, existing_input_keys_depth) existing_keys_index = get_key_descriptor_index(keys, existing_input_keys_depth)
remaining_keys = table.slice(keys, existing_keys_index, #keys) remaining_keys = table.slice(keys, existing_keys_index, #keys)
if remaining_keys[1].type == "array_index" then if remaining_keys[1].type == "array_index" then
local starting_keys = table.slice(keys, 1, existing_keys_index - 1) local starting_keys = table.slice(keys, 1, existing_keys_index - 1)
normalize_array_indexes(entries, starting_keys, remaining_keys[1]) normalize_array_indexes(entries, starting_keys, remaining_keys[1])
end end
end end
end end
local _writes = {} local _writes = {}
write_keys(remaining_keys, 1, _writes) write_keys(remaining_keys, 1, _writes)
local writes = {} local writes = {}
for ii=1, #_writes do for ii = 1, #_writes do
if _writes[ii] == true then if _writes[ii] == true then
-- Unwrap table -- Unwrap table
writes[#writes] = writes[#writes][1] writes[#writes] = writes[#writes][1]
else else
writes[#writes + 1] = _writes[ii] writes[#writes + 1] = _writes[ii]
end end
end end
-- Hacky way to jump to end of object -- Hacky way to jump to end of object
vim.api.nvim_win_set_cursor(0, {entry.position.line_number, entry.position.value_start}) vim.api.nvim_win_set_cursor(0, { entry.position.line_number, entry.position.value_start })
vim.cmd [[execute "normal %"]] vim.cmd([[execute "normal %"]])
local changes = #writes local changes = #writes
local start_line = vim.api.nvim_win_get_cursor(0)[1] - 1 local start_line = vim.api.nvim_win_get_cursor(0)[1] - 1
-- Add comma to previous JSON entry -- Add comma to previous JSON entry
if should_add_comma then if should_add_comma then
add_comma(buffer, start_line) add_comma(buffer, start_line)
end end
local new_start_line = expand_empty_object(buffer, start_line) local new_start_line = expand_empty_object(buffer, start_line)
if new_start_line ~= start_line then if new_start_line ~= start_line then
changes = changes + math.abs(new_start_line - start_line) changes = changes + math.abs(new_start_line - start_line)
start_line = new_start_line start_line = new_start_line
end end
-- Insert new lines -- Insert new lines
vim.api.nvim_buf_set_lines(buffer, start_line, start_line, false, writes) vim.api.nvim_buf_set_lines(buffer, start_line, start_line, false, writes)
-- Format lines -- Format lines
vim.api.nvim_win_set_cursor(0, {start_line, 1}) vim.api.nvim_win_set_cursor(0, { start_line, 1 })
vim.cmd('execute "normal =' .. changes .. 'j"') vim.cmd('execute "normal =' .. changes .. 'j"')
M:jump_to_cursor_helper(buffer) M:jump_to_cursor_helper(buffer)
end end
return M; return M

File diff suppressed because it is too large Load Diff

View File

@ -15,121 +15,120 @@
---@field key_start number ---@field key_start number
local PRIMITIVE_TYPES = { local PRIMITIVE_TYPES = {
string = true, string = true,
number = true, number = true,
boolean = true, boolean = true,
} }
local CONTAINS_CHILDREN_TYPES = { local CONTAINS_CHILDREN_TYPES = {
[2] = true, -- Module / Javascript Object [2] = true, -- Module / Javascript Object
[8] = true, -- Field [8] = true, -- Field
[18] = true, -- Array [18] = true, -- Array
[19] = true, -- Object [19] = true, -- Object
} }
local M = {} local M = {}
---@param entry JSONEntry ---@param entry JSONEntry
local function get_contents_from_json_value(entry) local function get_contents_from_json_value(entry)
local value = entry.value local value = entry.value
if type(value) == "table" then if type(value) == "table" then
-- Recursively get the contents of the table -- Recursively get the contents of the table
local contents = {} local contents = {}
for k, v in pairs(value) do for k, v in pairs(value) do
contents[k] = get_contents_from_json_value(v) contents[k] = get_contents_from_json_value(v)
end end
return contents return contents
else else
return entry.value return entry.value
end end
end end
---@param t table|nil|string|number|boolean ---@param t table|nil|string|number|boolean
---@return Entry[] ---@return Entry[]
function M:get_entries_from_lua_json(t) function M:get_entries_from_lua_json(t)
if PRIMITIVE_TYPES[type(t)] or t == nil then if PRIMITIVE_TYPES[type(t)] or t == nil then
return {} return {}
end end
local keys = {} local keys = {}
for k, _raw_value in pairs(t) do for k, _raw_value in pairs(t) do
---@type JSONEntry ---@type JSONEntry
local raw_value = _raw_value local raw_value = _raw_value
---@type Entry ---@type Entry
local entry = { local entry = {
key = tostring(k), key = tostring(k),
value = get_contents_from_json_value(raw_value), value = get_contents_from_json_value(raw_value),
position = { position = {
line_number = raw_value.line_number, line_number = raw_value.line_number,
key_start = raw_value.key_start, key_start = raw_value.key_start,
value_start = raw_value.value_start, value_start = raw_value.value_start,
} },
} }
table.insert(keys, entry) table.insert(keys, entry)
local v = raw_value.value local v = raw_value.value
if type(v) == "table" then if type(v) == "table" then
local sub_keys = M:get_entries_from_lua_json(v) local sub_keys = M:get_entries_from_lua_json(v)
for index=1, #sub_keys do for index = 1, #sub_keys do
local sub_key = sub_keys[index] local sub_key = sub_keys[index]
---@type Entry ---@type Entry
local entry = { local entry = {
key = k .. "." .. sub_key.key, key = k .. "." .. sub_key.key,
value = sub_key.value, value = sub_key.value,
position = sub_key.position, position = sub_key.position,
} }
keys[#keys + 1] = entry keys[#keys + 1] = entry
end end
end end
end end
return keys return keys
end end
---@param result Symbol ---@param result Symbol
---@return string|number|table|boolean|nil ---@return string|number|table|boolean|nil
function M:parse_lsp_value(result) function M:parse_lsp_value(result)
-- Object -- Object
if CONTAINS_CHILDREN_TYPES[result.kind] then if CONTAINS_CHILDREN_TYPES[result.kind] then
local value = {} local value = {}
for _, child in ipairs(result.children) do for _, child in ipairs(result.children) do
value[child.name] = M:parse_lsp_value(child) value[child.name] = M:parse_lsp_value(child)
end end
return value return value
-- Integer -- Integer
elseif result.kind == 16 then elseif result.kind == 16 then
return tonumber(result.detail) return tonumber(result.detail)
-- String -- String
elseif result.kind == 15 then elseif result.kind == 15 then
return result.detail return result.detail
-- Array -- Array
elseif result.kind == 18 then elseif result.kind == 18 then
local value = {} local value = {}
for i, child in ipairs(result.children) do for i, child in ipairs(result.children) do
value[i] = M:parse_lsp_value(child) value[i] = M:parse_lsp_value(child)
end end
return value return value
-- null -- null
elseif result.kind == 13 then elseif result.kind == 13 then
return nil return nil
-- boolean -- boolean
elseif result.kind == 17 then elseif result.kind == 17 then
return result.detail == "true" return result.detail == "true"
end end
end end
---@class Symbol ---@class Symbol
---@field name string ---@field name string
---@field kind number 2 = Object, 16 = Number, 15 = String, 18 = Array, 13 = Null, 17 = Boolean ---@field kind number 2 = Object, 16 = Number, 15 = String, 18 = Array, 13 = Null, 17 = Boolean
@ -149,45 +148,45 @@ end
---@param symbols Symbol[] ---@param symbols Symbol[]
---@return Entry[] ---@return Entry[]
function M:get_entries_from_lsp_symbols(symbols) function M:get_entries_from_lsp_symbols(symbols)
local keys = {} local keys = {}
for index=1, #symbols do for index = 1, #symbols do
local symbol = symbols[index] local symbol = symbols[index]
local key = symbol.name local key = symbol.name
---@type Entry ---@type Entry
local entry = { local entry = {
key = tostring(key), key = tostring(key),
value = M:parse_lsp_value(symbol), value = M:parse_lsp_value(symbol),
position = { position = {
line_number = symbol.range.start.line + 1, line_number = symbol.range.start.line + 1,
key_start = symbol.range.start.character + 2, 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 -- 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: -- We assume a default JSON file like:
-- `"my_key": "my_value"` -- `"my_key": "my_value"`
-- Since we get the end of the key, we can just add 4 to get the start of the 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, value_start = symbol.selectionRange["end"].character + 3,
} },
} }
keys[#keys + 1] = entry keys[#keys + 1] = entry
if CONTAINS_CHILDREN_TYPES[symbol.kind] then if CONTAINS_CHILDREN_TYPES[symbol.kind] then
local sub_keys = M:get_entries_from_lsp_symbols(symbol.children) local sub_keys = M:get_entries_from_lsp_symbols(symbol.children)
for jindex=1, #sub_keys do for jindex = 1, #sub_keys do
---@type Entry ---@type Entry
local entry = { local entry = {
key = key .. "." .. sub_keys[jindex].key, key = key .. "." .. sub_keys[jindex].key,
value = sub_keys[jindex].value, value = sub_keys[jindex].value,
position = sub_keys[jindex].position, position = sub_keys[jindex].position,
} }
keys[#keys + 1] = entry keys[#keys + 1] = entry
end end
end end
end end
return keys return keys
end end
return M return M

View File

@ -54,43 +54,43 @@
local M = {} local M = {}
function M:truncate_overflow(value, max_length, overflow_marker) function M:truncate_overflow(value, max_length, overflow_marker)
if vim.fn.strdisplaywidth(value) > max_length then if vim.fn.strdisplaywidth(value) > max_length then
return value:sub(1, max_length - vim.fn.strdisplaywidth(overflow_marker)) .. overflow_marker return value:sub(1, max_length - vim.fn.strdisplaywidth(overflow_marker)) .. overflow_marker
end end
return value return value
end end
---@param value any ---@param value any
---@param conceal boolean ---@param conceal boolean
---@param render_objects boolean ---@param render_objects boolean
function M:create_display_preview(value, conceal, render_objects) function M:create_display_preview(value, conceal, render_objects)
local t = type(value) local t = type(value)
if t == "table" then if t == "table" then
if render_objects == false then if render_objects == false then
return "", "other" return "", "other"
end end
local preview_table = {} local preview_table = {}
for k, v in pairs(value) do for k, v in pairs(value) do
preview_table[#preview_table + 1] = k .. ": " .. M:create_display_preview(v, conceal, render_objects) preview_table[#preview_table + 1] = k .. ": " .. M:create_display_preview(v, conceal, render_objects)
end end
return "{ " .. table.concat(preview_table, ", ") .. " }", "other" return "{ " .. table.concat(preview_table, ", ") .. " }", "other"
elseif t == "nil" then elseif t == "nil" then
return "null", "null" return "null", "null"
elseif t == "number" then elseif t == "number" then
return tostring(value), "number" return tostring(value), "number"
elseif t == "string" then elseif t == "string" then
if conceal then if conceal then
return value, "string" return value, "string"
else else
return "\"" .. value .. "\"", "string" return '"' .. value .. '"', "string"
end end
elseif t == "boolean" then elseif t == "boolean" then
return value and "true" or "false", "boolean" return value and "true" or "false", "boolean"
end end
end end
---@param key string ---@param key string
@ -99,110 +99,110 @@ end
---Replaces all previous keys with the replacement ---Replaces all previous keys with the replacement
---Example: replace_previous_keys("a.b.c", "x") => "xxx.c" ---Example: replace_previous_keys("a.b.c", "x") => "xxx.c"
function M:replace_previous_keys(key, replacement) function M:replace_previous_keys(key, replacement)
for i = #key, 1, -1 do for i = #key, 1, -1 do
if key:sub(i, i) == "." then if key:sub(i, i) == "." then
local len = i - 1 local len = i - 1
local before = replacement:rep(len) local before = replacement:rep(len)
return before .. "." .. key:sub(i + 1) return before .. "." .. key:sub(i + 1)
end end
end end
return key return key
end end
---@param text string ---@param text string
---@param char string ---@param char string
---@return string[] ---@return string[]
function M:split_by_char(text, char) function M:split_by_char(text, char)
local parts = {} local parts = {}
local current = "" local current = ""
for i = 1, #text do for i = 1, #text do
local c = text:sub(i, i) local c = text:sub(i, i)
if c == char then if c == char then
parts[#parts + 1] = current parts[#parts + 1] = current
current = "" current = ""
else else
current = current .. c current = current .. c
end end
end end
parts[#parts + 1] = current parts[#parts + 1] = current
return parts return parts
end end
---@param text string ---@param text string
---@return KeyDescription[] ---@return KeyDescription[]
function M:extract_key_description(text) 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 while index <= #splitted do
local token = splitted[index] local token = splitted[index]
-- Escape -- Escape
if string.sub(token, 1, 1) == "\\" then if string.sub(token, 1, 1) == "\\" then
token = token:sub(2) token = token:sub(2)
keys[#keys + 1] = { keys[#keys + 1] = {
type = "object_wrapper", type = "object_wrapper",
} }
keys[#keys + 1] = { keys[#keys + 1] = {
key = token, key = token,
type = "key", type = "key",
} }
-- Array -- Array
elseif string.match(token, "%[%d+%]") then elseif string.match(token, "%[%d+%]") then
local array_index = tonumber(string.sub(token, 2, -2)) local array_index = tonumber(string.sub(token, 2, -2))
keys[#keys + 1] = { keys[#keys + 1] = {
type = "array_wrapper", type = "array_wrapper",
} }
keys[#keys + 1] = { keys[#keys + 1] = {
key = array_index, key = array_index,
type = "array_index", type = "array_index",
} }
-- Array -- Array
elseif string.match(token, "%d+") then elseif string.match(token, "%d+") then
local array_index = tonumber(token) local array_index = tonumber(token)
keys[#keys + 1] = { keys[#keys + 1] = {
type = "array_wrapper", type = "array_wrapper",
} }
keys[#keys + 1] = { keys[#keys + 1] = {
key = array_index, key = array_index,
type = "array_index", type = "array_index",
} }
-- Object -- Object
else else
keys[#keys + 1] = { keys[#keys + 1] = {
type = "object_wrapper", type = "object_wrapper",
} }
keys[#keys + 1] = { keys[#keys + 1] = {
key = token, key = token,
type = "key", type = "key",
} }
end end
index = index + 1 index = index + 1
end end
if #keys == 0 then if #keys == 0 then
return { return {
{ {
key = text, key = text,
type = "key", type = "key",
} },
} }
end end
return keys return keys
end end
---@param name string ---@param name string

View File

@ -26,19 +26,19 @@
---@field null string - Highlight group for null values, Default: "@constant.builtin.json" ---@field null string - Highlight group for null values, Default: "@constant.builtin.json"
---@field other string - Highlight group for other types, Default: "@label.json" ---@field other string - Highlight group for other types, Default: "@label.json"
local parsers = require"jsonfly.parsers" local parsers = require("jsonfly.parsers")
local utils = require"jsonfly.utils" local utils = require("jsonfly.utils")
local cache = require"jsonfly.cache" local cache = require("jsonfly.cache")
local insert = require"jsonfly.insert" local insert = require("jsonfly.insert")
local json = require"jsonfly.json" local json = require("jsonfly.json")
local finders = require "telescope.finders" local finders = require("telescope.finders")
local pickers = require "telescope.pickers" local pickers = require("telescope.pickers")
local conf = require("telescope.config").values local conf = require("telescope.config").values
local make_entry = require "telescope.make_entry" local make_entry = require("telescope.make_entry")
local entry_display = require "telescope.pickers.entry_display" local entry_display = require("telescope.pickers.entry_display")
local action_state = require "telescope.actions.state" local action_state = require("telescope.actions.state")
---@type Options ---@type Options
local DEFAULT_CONFIG = { local DEFAULT_CONFIG = {
@ -79,43 +79,40 @@ local global_config = {}
---@param entries Entry[] ---@param entries Entry[]
---@param buffer number ---@param buffer number
local function show_picker(entries, buffer, xopts) local function show_picker(entries, buffer, xopts)
local config = vim.tbl_deep_extend("force", global_config, xopts or {}) local config = vim.tbl_deep_extend("force", global_config, xopts or {})
local filename = vim.api.nvim_buf_get_name(buffer) local filename = vim.api.nvim_buf_get_name(buffer)
local displayer = entry_display.create { local displayer = entry_display.create({
separator = " ", separator = " ",
items = { items = {
{ width = 1 }, { width = 1 },
global_config.key_exact_length and { width = global_config.key_max_length } or { remaining = true }, global_config.key_exact_length and { width = global_config.key_max_length } or { remaining = true },
{ remaining = true }, { remaining = true },
}, },
} })
---@type boolean ---@type boolean
local conceal local conceal
if global_config.conceal == "auto" then if global_config.conceal == "auto" then
conceal = vim.o.conceallevel > 0 conceal = vim.o.conceallevel > 0
else else
conceal = global_config.conceal == true conceal = global_config.conceal == true
end end
pickers.new(config, { pickers
prompt_title = global_config.prompt_title, .new(config, {
attach_mappings = function(_, map) prompt_title = global_config.prompt_title,
map( attach_mappings = function(_, map)
global_config.commands.add_key[1], map(global_config.commands.add_key[1], global_config.commands.add_key[2], function(prompt_bufnr)
global_config.commands.add_key[2], local current_picker = action_state.get_current_picker(prompt_bufnr)
function(prompt_bufnr) local input = current_picker:_get_prompt()
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) insert:insert_new_key(entries, key_descriptor, buffer)
end end)
)
if global_config.commands.copy_jsonpath and utils:is_module_available("jsonpath") then if global_config.commands.copy_jsonpath and utils:is_module_available("jsonpath") then
map( map(
global_config.commands.copy_jsonpath[1], global_config.commands.copy_jsonpath[1],
global_config.commands.copy_jsonpath[2], global_config.commands.copy_jsonpath[2],
@ -140,121 +137,146 @@ local function show_picker(entries, buffer, xopts)
) )
end end
return true return true
end, end,
finder = finders.new_table { finder = finders.new_table({
results = entries, results = entries,
---@param entry Entry ---@param entry Entry
entry_maker = function(entry) entry_maker = function(entry)
local _, raw_depth = entry.key:gsub("%.", ".") local _, raw_depth = entry.key:gsub("%.", ".")
local depth = (raw_depth or 0) + 1 local depth = (raw_depth or 0) + 1
return make_entry.set_default_entry_mt({ return make_entry.set_default_entry_mt({
value = buffer, value = buffer,
ordinal = entry.key, ordinal = entry.key,
display = function(_) display = function(_)
local preview, hl_group_key = utils:create_display_preview(entry.value, conceal, global_config.show_nested_child_preview) 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 { return displayer({
{ depth, "TelescopeResultsNumber"}, { depth, "TelescopeResultsNumber" },
{ {
utils:truncate_overflow( utils:truncate_overflow(
key, key,
global_config.key_max_length, global_config.key_max_length,
global_config.overflow_marker global_config.overflow_marker
), ),
"@property.json", "@property.json",
}, },
{ {
utils:truncate_overflow( utils:truncate_overflow(
preview, preview,
global_config.max_length, global_config.max_length,
global_config.overflow_marker global_config.overflow_marker
), ),
global_config.highlights[hl_group_key] or "TelescopeResultsString", global_config.highlights[hl_group_key] or "TelescopeResultsString",
}, },
} })
end, end,
bufnr = buffer, bufnr = buffer,
filename = filename, filename = filename,
lnum = entry.position.line_number, lnum = entry.position.line_number,
col = global_config.jump_behavior == "key_start" col = global_config.jump_behavior == "key_start" and entry.position.key_start
and entry.position.key_start -- Use length ("#" operator) as vim jumps to the bytes, not characters
-- Use length ("#" operator) as vim jumps to the bytes, not characters or entry.position.value_start,
or entry.position.value_start }, config)
}, config) end,
end, }),
}, previewer = conf.grep_previewer(config),
previewer = conf.grep_previewer(config), sorter = conf.generic_sorter(config),
sorter = conf.generic_sorter(config), sorting_strategy = "ascending",
sorting_strategy = "ascending", })
}):find() :find()
end end
return require("telescope").register_extension { return require("telescope").register_extension({
setup = function(extension_config) setup = function(extension_config)
global_config = vim.tbl_deep_extend("force", DEFAULT_CONFIG, extension_config or {}) global_config = vim.tbl_deep_extend("force", DEFAULT_CONFIG, extension_config or {})
end, end,
exports = { exports = {
jsonfly = function(xopts) jsonfly = function(xopts)
local current_buf = vim.api.nvim_get_current_buf() 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 if cached_entries ~= nil then
show_picker(cached_entries, current_buf, xopts) show_picker(cached_entries, current_buf, xopts)
return return
end end
local content_lines = vim.api.nvim_buf_get_lines(current_buf, 0, -1, false) local content_lines = vim.api.nvim_buf_get_lines(current_buf, 0, -1, false)
local content = table.concat(content_lines, "\n") local content = table.concat(content_lines, "\n")
local allow_cache = global_config.use_cache > 0 and #content_lines >= global_config.use_cache local allow_cache = global_config.use_cache > 0 and #content_lines >= global_config.use_cache
if allow_cache then if allow_cache then
cache:register_listeners(current_buf) cache:register_listeners(current_buf)
end end
local function run_lua_parser() local function run_lua_parser()
local parsed = json:decode(content) local parsed = json:decode(content)
local entries = parsers:get_entries_from_lua_json(parsed) local entries = parsers:get_entries_from_lua_json(parsed)
if allow_cache then if allow_cache then
cache:cache_buffer(current_buf, entries) cache:cache_buffer(current_buf, entries)
end end
show_picker(entries, current_buf, xopts) show_picker(entries, current_buf, xopts)
end end
if global_config.backend == "lsp" then if global_config.backend == "lsp" then
local params = vim.lsp.util.make_position_params(xopts.winnr) local params = vim.lsp.util.make_position_params(xopts.winnr)
vim.lsp.buf_request_all( -- Check i
current_buf, local clients = vim.lsp.get_clients()
"textDocument/documentSymbol",
params,
function(response)
if response == nil or #response == 0 then
run_lua_parser()
return
end
local result = response[1].result local any_support = false
local entries = parsers:get_entries_from_lsp_symbols(result) for _, client in ipairs(clients) do
if client.supports_method("textDocument/documentSymbol") then
any_support = true
break
end
end
if allow_cache then if any_support then
cache:cache_buffer(current_buf, entries) vim.lsp.buf_request_all(current_buf, "textDocument/documentSymbol", params, function(results)
end if results == nil or vim.tbl_isempty(results) then
run_lua_parser()
return
end
show_picker(entries, current_buf, xopts) local combined_result = {}
end
) for _, res in pairs(results) do
else if res.result then
run_lua_parser() vim.list_extend(combined_result, res.result)
end end
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,
},
})