mirror of
https://github.com/Myzel394/jsonfly.nvim.git
synced 2025-06-18 12:15:25 +02:00
commit
4796c4216b
@ -387,7 +387,7 @@ local function grok_object(self, text, start, options)
|
|||||||
---- Add start position so we can quickly jump to it
|
---- Add start position so we can quickly jump to it
|
||||||
VALUE[key] = {
|
VALUE[key] = {
|
||||||
value = new_val,
|
value = new_val,
|
||||||
newlines = newlines or 0,
|
line_number = (newlines or 0) + 1,
|
||||||
key_start = relative_start + 1,
|
key_start = relative_start + 1,
|
||||||
value_start = get_relative_i(text, i),
|
value_start = get_relative_i(text, i),
|
||||||
}
|
}
|
||||||
@ -440,7 +440,7 @@ local function grok_array(self, text, start, options)
|
|||||||
-- can't table.insert(VALUE, val) here because it's a no-op if val is nil
|
-- can't table.insert(VALUE, val) here because it's a no-op if val is nil
|
||||||
VALUE[VALUE_INDEX] = {
|
VALUE[VALUE_INDEX] = {
|
||||||
value = val,
|
value = val,
|
||||||
newlines = newlines or 0,
|
line_number = (newlines or 0) + 1,
|
||||||
value_start = relative_start,
|
value_start = relative_start,
|
||||||
key_start = relative_start,
|
key_start = relative_start,
|
||||||
}
|
}
|
||||||
|
169
lua/jsonfly/parsers.lua
Normal file
169
lua/jsonfly/parsers.lua
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
---@class EntryPosition
|
||||||
|
---@field line_number number
|
||||||
|
---@field key_start number
|
||||||
|
---@field value_start number
|
||||||
|
--
|
||||||
|
---@class Entry
|
||||||
|
---@field key string
|
||||||
|
---@field value Entry|table|number|string|boolean|nil
|
||||||
|
---@field position EntryPosition
|
||||||
|
--
|
||||||
|
---@class JSONEntry
|
||||||
|
---@field value JSONEntry|string|number|boolean|nil
|
||||||
|
---@field line_number number
|
||||||
|
---@field value_start number
|
||||||
|
---@field key_start number
|
||||||
|
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param entry JSONEntry
|
||||||
|
local function get_contents_from_json_value(entry)
|
||||||
|
local value = entry.value
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
return contents
|
||||||
|
else
|
||||||
|
return entry.value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param t table
|
||||||
|
---@return Entry[]
|
||||||
|
function M:get_entries_from_lua_json(t)
|
||||||
|
local keys = {}
|
||||||
|
|
||||||
|
for k, _raw_value in pairs(t) do
|
||||||
|
---@type JSONEntry
|
||||||
|
local raw_value = _raw_value
|
||||||
|
---@type Entry
|
||||||
|
local entry = {
|
||||||
|
key = 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
|
||||||
|
|
||||||
|
if type(v) == "table" then
|
||||||
|
local sub_keys = M:get_entries_from_lua_json(v)
|
||||||
|
|
||||||
|
for _, sub_key in ipairs(sub_keys) do
|
||||||
|
---@type Entry
|
||||||
|
local entry = {
|
||||||
|
key = k .. "." .. sub_key.key,
|
||||||
|
value = get_contents_from_json_value(sub_key),
|
||||||
|
position = sub_key.position,
|
||||||
|
}
|
||||||
|
|
||||||
|
table.insert(keys, entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return keys
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param result Symbol
|
||||||
|
---@return string|number|table|boolean|nil
|
||||||
|
function M:parse_lsp_value(result)
|
||||||
|
if result.kind == 2 then
|
||||||
|
local value = {}
|
||||||
|
|
||||||
|
for _, child in ipairs(result.children) do
|
||||||
|
value[child.name] = M:parse_lsp_value(child)
|
||||||
|
end
|
||||||
|
|
||||||
|
return value
|
||||||
|
elseif result.kind == 16 then
|
||||||
|
return tonumber(result.detail)
|
||||||
|
elseif result.kind == 15 then
|
||||||
|
return result.detail
|
||||||
|
elseif result.kind == 18 then
|
||||||
|
local value = {}
|
||||||
|
|
||||||
|
for i, child in ipairs(result.children) do
|
||||||
|
value[i] = M:parse_lsp_value(child)
|
||||||
|
end
|
||||||
|
|
||||||
|
return value
|
||||||
|
elseif result.kind == 13 then
|
||||||
|
return nil
|
||||||
|
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
|
||||||
|
---@field range Range
|
||||||
|
---@field selectionRange Range
|
||||||
|
---@field detail string
|
||||||
|
---@field children Symbol[]
|
||||||
|
--
|
||||||
|
---@class Range
|
||||||
|
---@field start Position
|
||||||
|
---@field ["end"] Position
|
||||||
|
--
|
||||||
|
---@class Position
|
||||||
|
---@field line number
|
||||||
|
---@field character number
|
||||||
|
--
|
||||||
|
---@param symbols Symbol[]
|
||||||
|
---@return Entry[]
|
||||||
|
function M:get_entries_from_lsp_symbols(symbols)
|
||||||
|
local keys = {}
|
||||||
|
|
||||||
|
for _, symbol in ipairs(symbols) do
|
||||||
|
local key = symbol.name
|
||||||
|
|
||||||
|
---@type Entry
|
||||||
|
local entry = {
|
||||||
|
key = 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.insert(keys, entry)
|
||||||
|
|
||||||
|
if symbol.kind == 2 then
|
||||||
|
local sub_keys = M:get_entries_from_lsp_symbols(symbol.children)
|
||||||
|
|
||||||
|
for _, sub_key in ipairs(sub_keys) do
|
||||||
|
---@type Entry
|
||||||
|
local entry = {
|
||||||
|
key = key .. "." .. sub_key.key,
|
||||||
|
value = sub_key.value,
|
||||||
|
position = sub_key.position,
|
||||||
|
}
|
||||||
|
|
||||||
|
table.insert(keys, entry)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return keys
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
64
lua/jsonfly/utils.lua
Normal file
64
lua/jsonfly/utils.lua
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param value any
|
||||||
|
---@param opts Options
|
||||||
|
function M:create_display_preview(value, opts)
|
||||||
|
local t = type(value)
|
||||||
|
local conceal
|
||||||
|
|
||||||
|
if opts.conceal == "auto" then
|
||||||
|
conceal = vim.o.conceallevel > 0
|
||||||
|
else
|
||||||
|
conceal = opts.conceal
|
||||||
|
end
|
||||||
|
|
||||||
|
if t == "table" then
|
||||||
|
local preview_table = {}
|
||||||
|
|
||||||
|
for k, v in pairs(value) do
|
||||||
|
table.insert(preview_table, k .. ": " .. M:create_display_preview(v, opts))
|
||||||
|
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
|
||||||
|
---@param replacement string
|
||||||
|
---@return string
|
||||||
|
---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)
|
||||||
|
|
||||||
|
return before .. "." .. key:sub(i + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return key
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
@ -1,3 +1,4 @@
|
|||||||
|
---- Documentation for jsonfly ----
|
||||||
--- Type definitions
|
--- Type definitions
|
||||||
---@class Options
|
---@class Options
|
||||||
---@field key_max_length number - Length for the key column, 0 for no column-like display, Default: 50
|
---@field key_max_length number - Length for the key column, 0 for no column-like display, Default: 50
|
||||||
@ -9,6 +10,7 @@
|
|||||||
---@field highlights Highlights - Highlight groups for different types
|
---@field highlights Highlights - Highlight groups for different types
|
||||||
---@field jump_behavior "key_start"|"value_start" - Behavior for jumping to the location, "key_start" == Jump to the start of the key, "value_start" == Jump to the start of the value, Default: "key_start"
|
---@field jump_behavior "key_start"|"value_start" - Behavior for jumping to the location, "key_start" == Jump to the start of the key, "value_start" == Jump to the start of the value, Default: "key_start"
|
||||||
---@field subkeys_display "normal"|"waterfall" - Display subkeys in a normal or waterfall style, Default: "normal"
|
---@field subkeys_display "normal"|"waterfall" - Display subkeys in a normal or waterfall style, Default: "normal"
|
||||||
|
---@field backend "lua"|"lsp" - Backend to use for parsing JSON, "lua" = Use our own Lua parser to parse the JSON, "lsp" = Use your LSP to parse the JSON (currently only https://github.com/Microsoft/vscode-json-languageservice is supported). If the "lsp" backend is selected but the LSP fails, it will fallback to the "lua" backend, Default: "lsp"
|
||||||
---
|
---
|
||||||
---@class Highlights
|
---@class Highlights
|
||||||
---@field number string - Highlight group for numbers, Default: "@number.json"
|
---@field number string - Highlight group for numbers, Default: "@number.json"
|
||||||
@ -17,93 +19,17 @@
|
|||||||
---@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 json = require"jsonfly.json"
|
||||||
|
local utils = require"jsonfly.utils"
|
||||||
|
|
||||||
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 function get_recursive_keys(t)
|
|
||||||
local keys = {}
|
|
||||||
|
|
||||||
for k, raw_value in pairs(t) do
|
|
||||||
table.insert(keys, {key = k, entry = raw_value})
|
|
||||||
|
|
||||||
local v = raw_value.value
|
|
||||||
|
|
||||||
if type(v) == "table" then
|
|
||||||
local sub_keys = get_recursive_keys(v)
|
|
||||||
for _, sub_key in ipairs(sub_keys) do
|
|
||||||
table.insert(keys, {key = k .. "." .. sub_key.key, entry = sub_key.entry})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return keys
|
|
||||||
end
|
|
||||||
|
|
||||||
local function 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
|
|
||||||
|
|
||||||
return value
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param value any
|
|
||||||
---@param opts Options
|
|
||||||
local function create_display_preview(value, opts)
|
|
||||||
local t = type(value)
|
|
||||||
local conceal
|
|
||||||
|
|
||||||
if opts.conceal == "auto" then
|
|
||||||
conceal = vim.o.conceallevel > 0
|
|
||||||
else
|
|
||||||
conceal = opts.conceal
|
|
||||||
end
|
|
||||||
|
|
||||||
if t == "table" then
|
|
||||||
local preview_table = {}
|
|
||||||
|
|
||||||
for k, v in pairs(value) do
|
|
||||||
table.insert(preview_table, k .. ": " .. create_display_preview(v.value, opts))
|
|
||||||
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
|
|
||||||
---@param replacement string
|
|
||||||
---@return string
|
|
||||||
---Replaces all previous keys with the replacement
|
|
||||||
---Example: replace_previous_keys("a.b.c", "x") => "xxx.c"
|
|
||||||
local function 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)
|
|
||||||
|
|
||||||
return before .. "." .. key:sub(i + 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return key
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type Options
|
---@type Options
|
||||||
local opts = {
|
local opts = {
|
||||||
key_max_length = 50,
|
key_max_length = 50,
|
||||||
@ -121,21 +47,13 @@ local opts = {
|
|||||||
},
|
},
|
||||||
jump_behavior = "key_start",
|
jump_behavior = "key_start",
|
||||||
subkeys_display = "normal",
|
subkeys_display = "normal",
|
||||||
|
backend = "lsp",
|
||||||
}
|
}
|
||||||
|
|
||||||
return require"telescope".register_extension {
|
---@param results Entry[]
|
||||||
setup = function(extension_config)
|
---@param buffer number
|
||||||
opts = vim.tbl_deep_extend("force", opts, extension_config or {})
|
local function show_picker(results, buffer)
|
||||||
end,
|
local filename = vim.api.nvim_buf_get_name(buffer)
|
||||||
exports = {
|
|
||||||
jsonfly = function()
|
|
||||||
local current_buf = vim.api.nvim_get_current_buf()
|
|
||||||
local filename = vim.api.nvim_buf_get_name(current_buf)
|
|
||||||
local content_lines = vim.api.nvim_buf_get_lines(current_buf, 0, -1, false)
|
|
||||||
local content = table.concat(content_lines, "\n")
|
|
||||||
|
|
||||||
local parsed = json:decode(content)
|
|
||||||
local keys = get_recursive_keys(parsed)
|
|
||||||
|
|
||||||
local displayer = entry_display.create {
|
local displayer = entry_display.create {
|
||||||
separator = " ",
|
separator = " ",
|
||||||
@ -149,23 +67,26 @@ return require"telescope".register_extension {
|
|||||||
pickers.new(opts, {
|
pickers.new(opts, {
|
||||||
prompt_title = opts.prompt_title,
|
prompt_title = opts.prompt_title,
|
||||||
finder = finders.new_table {
|
finder = finders.new_table {
|
||||||
results = keys,
|
results = results,
|
||||||
|
---@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 = current_buf,
|
value = buffer,
|
||||||
ordinal = entry.key,
|
ordinal = entry.key,
|
||||||
display = function(_)
|
display = function(_)
|
||||||
local preview, hl_group_key = create_display_preview(entry.entry.value, opts)
|
local preview, hl_group_key = utils:create_display_preview(entry.value, opts)
|
||||||
|
|
||||||
local key = opts.subkeys_display == "normal" and entry.key or replace_previous_keys(entry.key, " ")
|
local key = opts.subkeys_display == "normal" and entry.key or utils:replace_previous_keys(entry.key, " ")
|
||||||
|
|
||||||
|
print(vim.inspect(entry))
|
||||||
|
|
||||||
return displayer {
|
return displayer {
|
||||||
{ depth, "TelescopeResultsNumber"},
|
{ depth, "TelescopeResultsNumber"},
|
||||||
{
|
{
|
||||||
truncate_overflow(
|
utils:truncate_overflow(
|
||||||
key,
|
key,
|
||||||
opts.key_max_length,
|
opts.key_max_length,
|
||||||
opts.overflow_marker
|
opts.overflow_marker
|
||||||
@ -173,7 +94,7 @@ return require"telescope".register_extension {
|
|||||||
"@property.json",
|
"@property.json",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
truncate_overflow(
|
utils:truncate_overflow(
|
||||||
preview,
|
preview,
|
||||||
opts.max_length,
|
opts.max_length,
|
||||||
opts.overflow_marker
|
opts.overflow_marker
|
||||||
@ -183,13 +104,13 @@ return require"telescope".register_extension {
|
|||||||
}
|
}
|
||||||
end,
|
end,
|
||||||
|
|
||||||
bufnr = current_buf,
|
bufnr = buffer,
|
||||||
filename = filename,
|
filename = filename,
|
||||||
lnum = entry.entry.newlines + 1,
|
lnum = entry.position.line_number,
|
||||||
col = opts.jump_behavior == "key_start"
|
col = opts.jump_behavior == "key_start"
|
||||||
and entry.entry.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.entry.value_start
|
or entry.position.value_start
|
||||||
}, opts)
|
}, opts)
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
@ -198,5 +119,45 @@ return require"telescope".register_extension {
|
|||||||
sorting_strategy = "ascending",
|
sorting_strategy = "ascending",
|
||||||
}):find()
|
}):find()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return require"telescope".register_extension {
|
||||||
|
setup = function(extension_config)
|
||||||
|
opts = vim.tbl_deep_extend("force", opts, extension_config or {})
|
||||||
|
end,
|
||||||
|
exports = {
|
||||||
|
jsonfly = function(xopts)
|
||||||
|
local current_buf = vim.api.nvim_get_current_buf()
|
||||||
|
local content_lines = vim.api.nvim_buf_get_lines(current_buf, 0, -1, false)
|
||||||
|
local content = table.concat(content_lines, "\n")
|
||||||
|
|
||||||
|
function run_lua_parser()
|
||||||
|
local parsed = json:decode(content)
|
||||||
|
local keys = parsers:get_entries_from_lua_json(parsed)
|
||||||
|
|
||||||
|
show_picker(keys, current_buf)
|
||||||
|
end
|
||||||
|
|
||||||
|
if opts.backend == "lsp" then
|
||||||
|
local params = vim.lsp.util.make_position_params(xopts.winnr)
|
||||||
|
|
||||||
|
vim.lsp.buf_request(
|
||||||
|
current_buf,
|
||||||
|
"textDocument/documentSymbol",
|
||||||
|
params,
|
||||||
|
function(error, lsp_response)
|
||||||
|
if error then
|
||||||
|
run_lua_parser()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = parsers:get_entries_from_lsp_symbols(lsp_response)
|
||||||
|
|
||||||
|
show_picker(result, current_buf)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
else
|
||||||
|
run_lua_parser()
|
||||||
|
end
|
||||||
|
end
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user