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
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

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.
-- 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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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
---@param name string

View File

@ -26,19 +26,19 @@
---@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 = {
@ -79,43 +79,40 @@ 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)
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(
global_config.commands.copy_jsonpath[1],
global_config.commands.copy_jsonpath[2],
@ -140,121 +137,146 @@ local function show_picker(entries, buffer, xopts)
)
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(response)
if response == nil or #response == 0 then
run_lua_parser()
return
end
-- Check i
local clients = vim.lsp.get_clients()
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
cache:cache_buffer(current_buf, entries)
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
show_picker(entries, current_buf, xopts)
end
)
else
run_lua_parser()
end
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)
end
end
run_lua_parser()
end,
},
})