Compare commits

...

6 Commits

Author SHA1 Message Date
2346a75cae
feat(dev): Add flake.nix 2025-06-06 12:55:07 +02:00
0e5158a8d4
chore: apply stylua 2025-06-06 12:54:16 +02:00
895e9adfdf
fix: merge main 2025-06-06 12:43:58 +02:00
Myzel394
d713d9ef38
Merge pull request #20 from kletobias/custom-jsonfly
Fix: Handle Dictionary Responses in jsonfly.lua for Neovim 0.10.1
2024-09-30 16:22:17 +02:00
Myzel394
8fe79c2783
fix: Fix fallback behavior; apply stylua 2024-09-30 16:18:19 +02:00
Tobias Klein
cbff2a5659 fix(jsonfly.lua): resolve function buf_request_all to work with new nvim
syntax and version changes not accounted for.
Refactored LSP request handling in jsonfly.lua to combine results from multiple LSP responses and handle empty results more gracefully. Improved code readability and maintainability by restructuring the nested if-else blocks.
2024-09-29 15:55:36 +02:00
9 changed files with 1286 additions and 1168 deletions

61
flake.lock generated Normal file
View File

@ -0,0 +1,61 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1748929857,
"narHash": "sha256-lcZQ8RhsmhsK8u7LIFsJhsLh/pzR9yZ8yqpTzyGdj+Q=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "c2a03962b8e24e669fb37b7df10e7c79531ff1a4",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

43
flake.nix Normal file
View File

@ -0,0 +1,43 @@
{
description = "jsonfly";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
utils.url = "github:numtide/flake-utils";
};
outputs = { nixpkgs, utils, ... } @ inputs:
utils.lib.eachDefaultSystem(system:
let
pkgs = nixpkgs.legacyPackages.${system};
logo = pkgs.writeText "logo.txt" ''
'';
in
{
devShells.default = pkgs.mkShell {
packages = with pkgs; [
stylua
# If this ever fails, just remove it. It's just for the logo.
lolcat
];
shellHook = ''
cat ${logo} | lolcat
echo "";
echo "Welcome to the jsonfly.nvim development environment!";
echo "";
'';
};
}
);
}

10
justfile Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env just --justfile
set dotenv-load := true
_default:
just --list -u
lint:
stylua lua

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,
false,
{
on_lines = function() on_lines = function()
self:invalidate_buffer(buffer) self:invalidate_buffer(buffer)
end, end,
on_detach = function() on_detach = function()
self:invalidate_buffer(buffer) self:invalidate_buffer(buffer)
_listening_buffers[buffer] = nil; _listening_buffers[buffer] = nil
end, end,
} })
);
end end
return M; return M

View File

@ -1,11 +1,11 @@
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)
@ -84,7 +84,7 @@ 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
@ -100,7 +100,7 @@ local function write_keys(keys, index, 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
@ -132,12 +132,7 @@ local function add_comma(buffer, insertion_line)
-- 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
@ -156,14 +151,7 @@ local function add_comma(buffer, insertion_line)
-- 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,
line_number,
char_index,
line_number,
char_index,
{","}
)
return return
end end
end end
@ -179,16 +167,10 @@ local function expand_empty_object(buffer, line_number)
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,
line_number + 1,
false,
{
"{", "{",
"}" .. remaining_line "}" .. remaining_line,
} })
)
return line_number + 1 return line_number + 1
end end
@ -298,7 +280,7 @@ function M:jump_to_cursor_helper(buffer)
) )
-- -- 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
@ -307,7 +289,7 @@ end
---@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
@ -326,8 +308,8 @@ function M:insert_new_key(entries, keys, buffer)
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
@ -353,8 +335,8 @@ function M:insert_new_key(entries, keys, buffer)
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
@ -383,7 +365,7 @@ function M:insert_new_key(entries, keys, buffer)
-- 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
@ -409,4 +391,4 @@ function M:insert_new_key(entries, keys, buffer)
M:jump_to_cursor_helper(buffer) M:jump_to_cursor_helper(buffer)
end end
return M; return M

View File

@ -1,4 +1,4 @@
local VERSION = '20161109.21' -- version history at end of file local VERSION = "20161109.21" -- version history at end of file
local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20161109.21 ]-" local AUTHOR_NOTE = "-[ JSON.lua package by Jeffrey Friedl (http://regex.info/blog/lua/json) version 20161109.21 ]-"
local OBJDEF = { local OBJDEF = {
@ -6,13 +6,21 @@ local OBJDEF = {
AUTHOR_NOTE = AUTHOR_NOTE, AUTHOR_NOTE = AUTHOR_NOTE,
} }
local default_pretty_indent = " " local default_pretty_indent = " "
local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent } local default_pretty_options = { pretty = true, align_keys = false, indent = default_pretty_indent }
local isArray = { __tostring = function() return "JSON array" end } isArray.__index = isArray local isArray = {
local isObject = { __tostring = function() return "JSON object" end } isObject.__index = isObject __tostring = function()
return "JSON array"
end,
}
isArray.__index = isArray
local isObject = {
__tostring = function()
return "JSON object"
end,
}
isObject.__index = isObject
function OBJDEF:newArray(tbl) function OBJDEF:newArray(tbl)
return setmetatable(tbl or {}, isArray) return setmetatable(tbl or {}, isArray)
@ -22,36 +30,56 @@ function OBJDEF:newObject(tbl)
return setmetatable(tbl or {}, isObject) return setmetatable(tbl or {}, isObject)
end end
local function getnum(op) local function getnum(op)
return type(op) == 'number' and op or op.N return type(op) == "number" and op or op.N
end end
local isNumber = { local isNumber = {
__tostring = function(T) return T.S end, __tostring = function(T)
__unm = function(op) return getnum(op) end, return T.S
end,
__unm = function(op)
return getnum(op)
end,
__concat = function(op1, op2) return tostring(op1) .. tostring(op2) end, __concat = function(op1, op2)
__add = function(op1, op2) return getnum(op1) + getnum(op2) end, return tostring(op1) .. tostring(op2)
__sub = function(op1, op2) return getnum(op1) - getnum(op2) end, end,
__mul = function(op1, op2) return getnum(op1) * getnum(op2) end, __add = function(op1, op2)
__div = function(op1, op2) return getnum(op1) / getnum(op2) end, return getnum(op1) + getnum(op2)
__mod = function(op1, op2) return getnum(op1) % getnum(op2) end, end,
__pow = function(op1, op2) return getnum(op1) ^ getnum(op2) end, __sub = function(op1, op2)
__lt = function(op1, op2) return getnum(op1) < getnum(op2) end, return getnum(op1) - getnum(op2)
__eq = function(op1, op2) return getnum(op1) == getnum(op2) end, end,
__le = function(op1, op2) return getnum(op1) <= getnum(op2) end, __mul = function(op1, op2)
return getnum(op1) * getnum(op2)
end,
__div = function(op1, op2)
return getnum(op1) / getnum(op2)
end,
__mod = function(op1, op2)
return getnum(op1) % getnum(op2)
end,
__pow = function(op1, op2)
return getnum(op1) ^ getnum(op2)
end,
__lt = function(op1, op2)
return getnum(op1) < getnum(op2)
end,
__eq = function(op1, op2)
return getnum(op1) == getnum(op2)
end,
__le = function(op1, op2)
return getnum(op1) <= getnum(op2)
end,
} }
isNumber.__index = isNumber isNumber.__index = isNumber
function OBJDEF:asNumber(item) function OBJDEF:asNumber(item)
if getmetatable(item) == isNumber then if getmetatable(item) == isNumber then
-- it's already a JSON number object. -- it's already a JSON number object.
return item return item
elseif type(item) == 'table' and type(item.S) == 'string' and type(item.N) == 'number' then elseif type(item) == "table" and type(item.S) == "string" and type(item.N) == "number" then
-- it's a number-object table that lost its metatable, so give it one -- it's a number-object table that lost its metatable, so give it one
return setmetatable(item, isNumber) return setmetatable(item, isNumber)
else else
@ -65,7 +93,7 @@ function OBJDEF:asNumber(item)
end end
function OBJDEF:forceString(item) function OBJDEF:forceString(item)
if type(item) == 'table' and type(item.S) == 'string' then if type(item) == "table" and type(item.S) == "string" then
return item.S return item.S
else else
return tostring(item) return tostring(item)
@ -73,30 +101,26 @@ function OBJDEF:forceString(item)
end end
function OBJDEF:forceNumber(item) function OBJDEF:forceNumber(item)
if type(item) == 'table' and type(item.N) == 'number' then if type(item) == "table" and type(item.N) == "number" then
return item.N return item.N
else else
return tonumber(item) return tonumber(item)
end end
end end
local function unicode_codepoint_as_utf8(codepoint) local function unicode_codepoint_as_utf8(codepoint)
-- --
-- codepoint is a number -- codepoint is a number
-- --
if codepoint <= 127 then if codepoint <= 127 then
return string.char(codepoint) return string.char(codepoint)
elseif codepoint <= 2047 then elseif codepoint <= 2047 then
-- --
-- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8 -- 110yyyxx 10xxxxxx <-- useful notation from http://en.wikipedia.org/wiki/Utf8
-- --
local highpart = math.floor(codepoint / 0x40) local highpart = math.floor(codepoint / 0x40)
local lowpart = codepoint - (0x40 * highpart) local lowpart = codepoint - (0x40 * highpart)
return string.char(0xC0 + highpart, return string.char(0xC0 + highpart, 0x80 + lowpart)
0x80 + lowpart)
elseif codepoint <= 65535 then elseif codepoint <= 65535 then
-- --
-- 1110yyyy 10yyyyxx 10xxxxxx -- 1110yyyy 10yyyyxx 10xxxxxx
@ -114,18 +138,16 @@ local function unicode_codepoint_as_utf8(codepoint)
-- Check for an invalid character (thanks Andy R. at Adobe). -- Check for an invalid character (thanks Andy R. at Adobe).
-- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070 -- See table 3.7, page 93, in http://www.unicode.org/versions/Unicode5.2.0/ch03.pdf#G28070
-- --
if ( highpart == 0xE0 and midpart < 0xA0 ) or if
( highpart == 0xED and midpart > 0x9F ) or (highpart == 0xE0 and midpart < 0xA0)
( highpart == 0xF0 and midpart < 0x90 ) or or (highpart == 0xED and midpart > 0x9F)
( highpart == 0xF4 and midpart > 0x8F ) or (highpart == 0xF0 and midpart < 0x90)
or (highpart == 0xF4 and midpart > 0x8F)
then then
return "?" return "?"
else else
return string.char(highpart, return string.char(highpart, midpart, lowpart)
midpart,
lowpart)
end end
else else
-- --
-- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx -- 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx
@ -137,10 +159,7 @@ local function unicode_codepoint_as_utf8(codepoint)
local midB = math.floor(remainder / 0x40) local midB = math.floor(remainder / 0x40)
local lowpart = remainder - 0x40 * midB local lowpart = remainder - 0x40 * midB
return string.char(0xF0 + highpart, return string.char(0xF0 + highpart, 0x80 + midA, 0x80 + midB, 0x80 + lowpart)
0x80 + midA,
0x80 + midB,
0x80 + lowpart)
end end
end end
@ -187,8 +206,7 @@ local function grok_number(self, text, start, options)
-- --
-- Grab the integer part -- Grab the integer part
-- --
local integer_part = text:match('^-?[1-9]%d*', start) local integer_part = text:match("^-?[1-9]%d*", start) or text:match("^-?0", start)
or text:match("^-?0", start)
if not integer_part then if not integer_part then
self:onDecodeError("expected number", text, start, options.etc) self:onDecodeError("expected number", text, start, options.etc)
@ -200,14 +218,14 @@ local function grok_number(self, text, start, options)
-- --
-- Grab an optional decimal part -- Grab an optional decimal part
-- --
local decimal_part = text:match('^%.%d+', i) or "" local decimal_part = text:match("^%.%d+", i) or ""
i = i + decimal_part:len() i = i + decimal_part:len()
-- --
-- Grab an optional exponential part -- Grab an optional exponential part
-- --
local exponent_part = text:match('^[eE][-+]?%d+', i) or "" local exponent_part = text:match("^[eE][-+]?%d+", i) or ""
i = i + exponent_part:len() i = i + exponent_part:len()
@ -223,21 +241,19 @@ local function grok_number(self, text, start, options)
-- I suppose we should really look to see whether the exponent is actually big enough one -- I suppose we should really look to see whether the exponent is actually big enough one
-- way or the other to trip stringification, but I'll be lazy about it until someone asks. -- way or the other to trip stringification, but I'll be lazy about it until someone asks.
-- --
if (options.decodeIntegerStringificationLength if
and (
(integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0)) options.decodeIntegerStringificationLength
and (integer_part:len() >= options.decodeIntegerStringificationLength or exponent_part:len() > 0)
or )
or (
(options.decodeDecimalStringificationLength options.decodeDecimalStringificationLength
and and (decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0)
(decimal_part:len() >= options.decodeDecimalStringificationLength or exponent_part:len() > 0)) )
then then
return full_number_text, i -- this returns the exact string representation seen in the original JSON return full_number_text, i -- this returns the exact string representation seen in the original JSON
end end
local as_number = tonumber(full_number_text) local as_number = tonumber(full_number_text)
if not as_number then if not as_number then
@ -248,9 +264,7 @@ local function grok_number(self, text, start, options)
return as_number, i return as_number, i
end end
local function grok_string(self, text, start, options) local function grok_string(self, text, start, options)
if text:sub(start, start) ~= '"' then if text:sub(start, start) ~= '"' then
self:onDecodeError("expected string's opening quote", text, start, options.etc) self:onDecodeError("expected string's opening quote", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible return nil, start -- in case the error method doesn't abort, return something sensible
@ -264,26 +278,29 @@ local function grok_string(self, text, start, options)
if c == '"' then if c == '"' then
return VALUE, i + 1 return VALUE, i + 1
end end
if c ~= '\\' then if c ~= "\\" then
VALUE = VALUE .. c VALUE = VALUE .. c
i = i + 1 i = i + 1
elseif text:match('^\\b', i) then elseif text:match("^\\b", i) then
VALUE = VALUE .. "\b" VALUE = VALUE .. "\b"
i = i + 2 i = i + 2
elseif text:match('^\\f', i) then elseif text:match("^\\f", i) then
VALUE = VALUE .. "\f" VALUE = VALUE .. "\f"
i = i + 2 i = i + 2
elseif text:match('^\\n', i) then elseif text:match("^\\n", i) then
VALUE = VALUE .. "\n" VALUE = VALUE .. "\n"
i = i + 2 i = i + 2
elseif text:match('^\\r', i) then elseif text:match("^\\r", i) then
VALUE = VALUE .. "\r" VALUE = VALUE .. "\r"
i = i + 2 i = i + 2
elseif text:match('^\\t', i) then elseif text:match("^\\t", i) then
VALUE = VALUE .. "\t" VALUE = VALUE .. "\t"
i = i + 2 i = i + 2
else else
local hex = text:match('^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) local hex = text:match(
"^\\u([0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])",
i
)
if hex then if hex then
i = i + 6 -- bypass what we just read i = i + 6 -- bypass what we just read
@ -292,7 +309,8 @@ local function grok_string(self, text, start, options)
local codepoint = tonumber(hex, 16) local codepoint = tonumber(hex, 16)
if codepoint >= 0xD800 and codepoint <= 0xDBFF then if codepoint >= 0xD800 and codepoint <= 0xDBFF then
-- it's a hi surrogate... see whether we have a following low -- it's a hi surrogate... see whether we have a following low
local lo_surrogate = text:match('^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])', i) local lo_surrogate =
text:match("^\\u([dD][cdefCDEF][0123456789aAbBcCdDeEfF][0123456789aAbBcCdDeEfF])", i)
if lo_surrogate then if lo_surrogate then
i = i + 6 -- bypass the low surrogate we just read i = i + 6 -- bypass the low surrogate we just read
codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16) codepoint = 0x2400 + (codepoint - 0xD800) * 0x400 + tonumber(lo_surrogate, 16)
@ -301,11 +319,9 @@ local function grok_string(self, text, start, options)
end end
end end
VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint) VALUE = VALUE .. unicode_codepoint_as_utf8(codepoint)
else else
-- just pass through what's escaped -- just pass through what's escaped
VALUE = VALUE .. text:match('^\\(.)', i) VALUE = VALUE .. text:match("^\\(.)", i)
i = i + 2 i = i + 2
end end
end end
@ -326,7 +342,7 @@ end
-- Count newlines in `text` up to `start` -- Count newlines in `text` up to `start`
local function count_newlines(text, start) local function count_newlines(text, start)
local _, count = text:sub(1, start):gsub('\n', '\n') local _, count = text:sub(1, start):gsub("\n", "\n")
return count return count
end end
@ -339,7 +355,7 @@ local function get_relative_i(text, i)
local count = 0 local count = 0
for j = i, 1, -1 do for j = i, 1, -1 do
if text:sub(j, j) == '\n' then if text:sub(j, j) == "\n" then
break break
end end
count = count + 1 count = count + 1
@ -351,17 +367,16 @@ end
local grok_one -- assigned later local grok_one -- assigned later
local function grok_object(self, text, start, options) local function grok_object(self, text, start, options)
if text:sub(start, start) ~= "{" then
if text:sub(start,start) ~= '{' then
self:onDecodeError("expected '{'", text, start, options.etc) self:onDecodeError("expected '{'", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible return nil, start -- in case the error method doesn't abort, return something sensible
end end
local i = skip_whitespace(text, start + 1) -- +1 to skip the '{' local i = skip_whitespace(text, start + 1) -- +1 to skip the '{'
local VALUE = self.strictTypes and self:newObject { } or { } local VALUE = self.strictTypes and self:newObject({}) or {}
if text:sub(i,i) == '}' then if text:sub(i, i) == "}" then
return VALUE, i + 1 return VALUE, i + 1
end end
local text_len = text:len() local text_len = text:len()
@ -375,7 +390,7 @@ local function grok_object(self, text, start, options)
i = skip_whitespace(text, new_i) i = skip_whitespace(text, new_i)
if text:sub(i, i) ~= ':' then if text:sub(i, i) ~= ":" then
self:onDecodeError("expected colon", text, i, options.etc) self:onDecodeError("expected colon", text, i, options.etc)
return nil, i -- in case the error method doesn't abort, return something sensible return nil, i -- in case the error method doesn't abort, return something sensible
end end
@ -399,11 +414,11 @@ local function grok_object(self, text, start, options)
local c = text:sub(i, i) local c = text:sub(i, i)
if c == '}' then if c == "}" then
return VALUE, i + 1 return VALUE, i + 1
end end
if text:sub(i, i) ~= ',' then if text:sub(i, i) ~= "," then
self:onDecodeError("expected comma or '}'", text, i, options.etc) self:onDecodeError("expected comma or '}'", text, i, options.etc)
return nil, i -- in case the error method doesn't abort, return something sensible return nil, i -- in case the error method doesn't abort, return something sensible
end end
@ -416,14 +431,14 @@ local function grok_object(self, text, start, options)
end end
local function grok_array(self, text, start, options) local function grok_array(self, text, start, options)
if text:sub(start,start) ~= '[' then if text:sub(start, start) ~= "[" then
self:onDecodeError("expected '['", text, start, options.etc) self:onDecodeError("expected '['", text, start, options.etc)
return nil, start -- in case the error method doesn't abort, return something sensible return nil, start -- in case the error method doesn't abort, return something sensible
end end
local i = skip_whitespace(text, start + 1) -- +1 to skip the '[' local i = skip_whitespace(text, start + 1) -- +1 to skip the '['
local VALUE = self.strictTypes and self:newArray { } or { } local VALUE = self.strictTypes and self:newArray({}) or {}
if text:sub(i,i) == ']' then if text:sub(i, i) == "]" then
return VALUE, i + 1 return VALUE, i + 1
end end
@ -450,10 +465,10 @@ local function grok_array(self, text, start, options)
-- Expect now either ']' to end things, or a ',' to allow us to continue. -- Expect now either ']' to end things, or a ',' to allow us to continue.
-- --
local c = text:sub(i, i) local c = text:sub(i, i)
if c == ']' then if c == "]" then
return VALUE, i + 1 return VALUE, i + 1
end end
if text:sub(i, i) ~= ',' then if text:sub(i, i) ~= "," then
self:onDecodeError("expected comma or ']'", text, i, options.etc) self:onDecodeError("expected comma or ']'", text, i, options.etc)
return nil, i -- in case the error method doesn't abort, return something sensible return nil, i -- in case the error method doesn't abort, return something sensible
end end
@ -463,7 +478,6 @@ local function grok_array(self, text, start, options)
return nil, i -- in case the error method doesn't abort, return something sensible return nil, i -- in case the error method doesn't abort, return something sensible
end end
grok_one = function(self, text, start, options) grok_one = function(self, text, start, options)
-- Skip any whitespace -- Skip any whitespace
start = skip_whitespace(text, start) start = skip_whitespace(text, start)
@ -475,25 +489,18 @@ grok_one = function(self, text, start, options)
if text:find('^"', start) then if text:find('^"', start) then
return grok_string(self, text, start, options.etc) return grok_string(self, text, start, options.etc)
elseif text:find("^[-0123456789 ]", start) then
elseif text:find('^[-0123456789 ]', start) then
return grok_number(self, text, start, options) return grok_number(self, text, start, options)
elseif text:find("^%{", start) then
elseif text:find('^%{', start) then
return grok_object(self, text, start, options) return grok_object(self, text, start, options)
elseif text:find("^%[", start) then
elseif text:find('^%[', start) then
return grok_array(self, text, start, options) return grok_array(self, text, start, options)
elseif text:find("^true", start) then
elseif text:find('^true', start) then
return true, start + 4 return true, start + 4
elseif text:find("^false", start) then
elseif text:find('^false', start) then
return false, start + 5 return false, start + 5
elseif text:find("^null", start) then
elseif text:find('^null', start) then
return nil, start + 4 return nil, start + 4
else else
self:onDecodeError("can't parse JSON", text, start, options.etc) self:onDecodeError("can't parse JSON", text, start, options.etc)
return nil, 1 -- in case the error method doesn't abort, return something sensible return nil, 1 -- in case the error method doesn't abort, return something sensible
@ -504,7 +511,7 @@ function OBJDEF:decode(text, etc, options)
-- --
-- If the user didn't pass in a table of decode options, make an empty one. -- If the user didn't pass in a table of decode options, make an empty one.
-- --
if type(options) ~= 'table' then if type(options) ~= "table" then
options = {} options = {}
end end
@ -516,12 +523,12 @@ function OBJDEF:decode(text, etc, options)
options.etc = etc options.etc = etc
end end
if text:match('^%s*$') then if text:match("^%s*$") then
-- an empty string is nothing, but not an error -- an empty string is nothing, but not an error
return nil return nil
end end
if text:match('^%s*<') then if text:match("^%s*<") then
-- Can't be JSON... we'll assume it's HTML -- Can't be JSON... we'll assume it's HTML
local error_message = "HTML passed to JSON:decode()" local error_message = "HTML passed to JSON:decode()"
self:onDecodeOfHTMLError(error_message, text, nil, options.etc) self:onDecodeOfHTMLError(error_message, text, nil, options.etc)
@ -558,7 +565,6 @@ function OBJDEF:decode(text, etc, options)
local success, value, next_i = pcall(grok_one, self, text, 1, options) local success, value, next_i = pcall(grok_one, self, text, 1, options)
if success then if success then
local error_message = nil local error_message = nil
if next_i ~= #text + 1 then if next_i ~= #text + 1 then
-- something's left over after we parsed the first thing.... whitespace is allowed. -- something's left over after we parsed the first thing.... whitespace is allowed.
@ -570,9 +576,7 @@ function OBJDEF:decode(text, etc, options)
end end
end end
return value, error_message return value, error_message
else else
-- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received -- If JSON:onDecodeError() didn't abort out of the pcall, we'll have received
-- the error message here as "value", so pass it along as an assert. -- the error message here as "value", so pass it along as an assert.
local error_message = value local error_message = value
@ -584,7 +588,6 @@ function OBJDEF:decode(text, etc, options)
-- ...and if we're still here (because the assert didn't throw an error), -- ...and if we're still here (because the assert didn't throw an error),
-- return a nil and throw the error message on as a second arg -- return a nil and throw the error message on as a second arg
return nil, error_message return nil, error_message
end end
end end
@ -607,5 +610,3 @@ function OBJDEF:new(args)
end end
return OBJDEF:new() return OBJDEF:new()

View File

@ -66,7 +66,7 @@ function M:get_entries_from_lua_json(t)
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)
@ -129,7 +129,6 @@ function M:parse_lsp_value(result)
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
@ -167,7 +166,7 @@ function M:get_entries_from_lsp_symbols(symbols)
-- `"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

View File

@ -86,7 +86,7 @@ function M:create_display_preview(value, conceal, render_objects)
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"
@ -198,7 +198,7 @@ function M:extract_key_description(text)
{ {
key = text, key = text,
type = "key", type = "key",
} },
} }
end end
@ -208,7 +208,9 @@ end
---@param name string ---@param name string
---@return boolean ---@return boolean
function M:is_module_available(name) function M:is_module_available(name)
return pcall(function() require(name) end) == true return pcall(function()
require(name)
end) == true
end end
return M return M

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 = {
@ -69,9 +69,9 @@ local DEFAULT_CONFIG = {
---@param prompt_bufnr number ---@param prompt_bufnr number
function(path, prompt_bufnr) function(path, prompt_bufnr)
vim.fn.setreg("+", path) vim.fn.setreg("+", path)
end end,
} },
} },
} }
local global_config = {} local global_config = {}
@ -82,14 +82,14 @@ 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
@ -99,21 +99,18 @@ local function show_picker(entries, buffer, xopts)
conceal = global_config.conceal == true conceal = global_config.conceal == true
end end
pickers.new(config, { pickers
.new(config, {
prompt_title = global_config.prompt_title, prompt_title = global_config.prompt_title,
attach_mappings = function(_, map) attach_mappings = function(_, map)
map( map(global_config.commands.add_key[1], global_config.commands.add_key[2], function(prompt_bufnr)
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 current_picker = action_state.get_current_picker(prompt_bufnr)
local input = current_picker:_get_prompt() 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(
@ -125,13 +122,16 @@ local function show_picker(entries, buffer, xopts)
local current_picker = action_state.get_current_picker(prompt_bufnr) local current_picker = action_state.get_current_picker(prompt_bufnr)
local selection = current_picker:get_selection() local selection = current_picker:get_selection()
local path = jsonpath.get(vim.treesitter.get_node({ local path = jsonpath.get(
vim.treesitter.get_node({
bufnr = buffer, bufnr = buffer,
pos = { pos = {
selection.lnum - 1, selection.lnum - 1,
selection.index, selection.index,
} },
}), buffer) }),
buffer
)
if path then if path then
global_config.commands.copy_jsonpath[3](path, prompt_bufnr) global_config.commands.copy_jsonpath[3](path, prompt_bufnr)
@ -142,7 +142,7 @@ local function show_picker(entries, buffer, xopts)
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)
@ -153,11 +153,16 @@ local function show_picker(entries, buffer, xopts)
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(
@ -175,26 +180,26 @@ local function show_picker(entries, buffer, xopts)
), ),
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,
@ -231,30 +236,50 @@ return require("telescope").register_extension {
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, local any_support = false
function(response)
if response == nil or #response == 0 then for _, client in ipairs(clients) do
if client.supports_method("textDocument/documentSymbol") then
any_support = true
break
end
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() run_lua_parser()
return return
end end
local result = response[1].result local combined_result = {}
local entries = parsers:get_entries_from_lsp_symbols(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 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 end
) end
else
run_lua_parser() run_lua_parser()
end end,
end },
} })
}