Skip to content

Commit f2e3de1

Browse files
committed
WIP
1 parent c95bbef commit f2e3de1

File tree

5 files changed

+214
-8
lines changed

5 files changed

+214
-8
lines changed

lua/lspsettings/config.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
--- @class Config
22
Config = {
3+
json5 = true,
34
paths = {
45
vim.fs.joinpath(vim.fn.stdpath('config'), "lspsettings"),
56
-- compatibility with `nlsp-settings` plugin

lua/lspsettings/json5.lua

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
---@diagnostic disable: lowercase-global
2+
3+
--- @param str string Stringified JSON5 data
4+
--- @param opts table<string,any> Options similar to `vim.json.decode`. Not used for now
5+
---@diagnostic disable-next-line: unused-local
6+
local function decode(str, opts)
7+
local function tokenize(input)
8+
local tokens = {}
9+
local i = 1
10+
local len = #input
11+
12+
local function skip_whitespace()
13+
while i <= len do
14+
local c = input:sub(i, i)
15+
if c:match("[%s\n\r\t]") then
16+
i = i + 1
17+
elseif c == "/" and input:sub(i + 1, i + 1) == "/" then
18+
i = i + 2
19+
while i <= len and input:sub(i, i) ~= "\n" do
20+
i = i + 1
21+
end
22+
elseif c == "/" and input:sub(i + 1, i + 1) == "*" then
23+
i = i + 2
24+
while i <= len - 1 and not (input:sub(i, i + 1) == "*/") do
25+
i = i + 1
26+
end
27+
i = i + 2
28+
else
29+
break
30+
end
31+
end
32+
end
33+
34+
local function read_line(quote)
35+
i = i + 1
36+
local output = ""
37+
while i <= len do
38+
local c = input:sub(i, i)
39+
if c == "\\" then
40+
local nextc = input:sub(i + 1, i + 1)
41+
if nextc == "n" then
42+
output = output .. "\n"
43+
elseif nextc == "t" then
44+
output = output .. "\t"
45+
elseif nextc == "r" then
46+
output = output .. "\r"
47+
elseif nextc == quote then
48+
output = output .. quote
49+
else
50+
output = output .. nextc
51+
end
52+
i = i + 2
53+
elseif c == quote then
54+
i = i + 1
55+
break
56+
else
57+
output = output .. c
58+
i = i + 1
59+
end
60+
end
61+
return { type = "string", value = output }
62+
end
63+
64+
local function read_number()
65+
local start = i
66+
while i <= len and input:sub(i, i):match("[%+%-%.%deE]") do
67+
i = i + 1
68+
end
69+
local num_str = input:sub(start, i - 1)
70+
if num_str == "NaN" then
71+
return { type = "number", value = 0 / 0 }
72+
elseif num_str == "Infinity" or num_str == "+Infinity" then
73+
return { type = "number", value = math.huge }
74+
elseif num_str == "-Infinity" then
75+
return { type = "number", value = -math.huge }
76+
else
77+
return { type = "number", value = tonumber(num_str) }
78+
end
79+
end
80+
81+
local function read_identifier()
82+
local start = i
83+
while i <= len and input:sub(i, i):match("[%w%$_]") do
84+
i = i + 1
85+
end
86+
local word = input:sub(start, i - 1)
87+
if word == "true" then
88+
return { type = "boolean", value = true }
89+
elseif word == "false" then
90+
return { type = "boolean", value = false }
91+
elseif word == "null" then
92+
return { type = "null", value = nil }
93+
elseif word == "NaN" or word == "Infinity" or word == "-Infinity" then
94+
i = start
95+
return read_number()
96+
else
97+
return { type = "identifier", value = word }
98+
end
99+
end
100+
101+
while i <= len do
102+
skip_whitespace()
103+
local c = input:sub(i, i)
104+
if c == "" then
105+
break
106+
elseif c == "{" or c == "}" or c == "[" or c == "]" or c == ":" or c == "," then
107+
table.insert(tokens, { type = "symbol", value = c })
108+
i = i + 1
109+
elseif c == '"' or c == "'" then
110+
table.insert(tokens, read_line(c))
111+
elseif c:match("[%+%-%.%d]") then
112+
table.insert(tokens, read_number())
113+
elseif c:match("[%a$_]") then
114+
table.insert(tokens, read_identifier())
115+
else
116+
error("Unexpected character: " .. c)
117+
end
118+
end
119+
120+
return tokens
121+
end
122+
123+
local function parse(tokens)
124+
local pos = 1
125+
126+
local function peek() return tokens[pos] end
127+
local function next_tok()
128+
local token = peek()
129+
pos = pos + 1
130+
return token
131+
end
132+
133+
local function parse_value()
134+
local token = peek()
135+
if not token then error("Unexpected end") end
136+
if token.type == "string" or token.type == "number" or token.type == "boolean" or token.type == "null" then
137+
return next_tok().value
138+
elseif token.value == "{" then
139+
return parse_object()
140+
elseif token.value == "[" then
141+
return parse_array()
142+
elseif token.type == "identifier" then
143+
return next_tok().value
144+
else
145+
error("Unexpected token: " .. token.value)
146+
end
147+
end
148+
149+
function parse_array()
150+
local result = {}
151+
assert(next_tok().value == "[")
152+
while peek() and peek().value ~= "]" do
153+
table.insert(result, parse_value())
154+
if peek().value == "," then next_tok() end
155+
end
156+
assert(next_tok().value == "]")
157+
return result
158+
end
159+
160+
function parse_object()
161+
local result = {}
162+
assert(next_tok().value == "{")
163+
while peek() and peek().value ~= "}" do
164+
local key_token = next_tok()
165+
local key
166+
if key_token.type == "string" or key_token.type == "identifier" then
167+
key = key_token.value
168+
else
169+
error("Invalid object key")
170+
end
171+
assert(next_tok().value == ":")
172+
local val = parse_value()
173+
result[key] = val
174+
if peek().value == "," then next_tok() end
175+
end
176+
assert(next_tok().value == "}")
177+
return result
178+
end
179+
180+
return parse_value()
181+
end
182+
183+
local tokens = tokenize(str)
184+
185+
return parse(tokens)
186+
end
187+
188+
189+
return {
190+
decode = decode,
191+
}

lua/lspsettings/loader.lua

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,25 @@ end
9292
--- @param server_name string
9393
--- @return table
9494
function JsonLoader:load(server_name)
95+
local decode = vim.json.decode
96+
if self.config.json5 then
97+
decode = require("lspsettings.json5").decode
98+
end
99+
95100
local settings = {}
96101

97102
-- reading each config
98103
for _, path in ipairs(self:list_server_configs(server_name)) do
99-
local jsoned = table.concat(vim.fn.readfile(path))
100-
local opts = { luanil = { object = true, array = true } }
101-
local success, data = pcall(vim.json.decode, jsoned, opts)
102-
103-
if success then
104-
settings = vim.tbl_extend("force", settings, data)
105-
else
106-
vim.notify("Unable to load LSP settings at `" .. path .. "`: " .. vim.inspect(data), vim.log.levels.WARN)
104+
local jsoned = table.concat(vim.fn.readfile(path), "\n")
105+
if jsoned ~= "" then
106+
local opts = { luanil = { object = true, array = true } }
107+
local success, data = pcall(decode, jsoned, opts)
108+
109+
if success then
110+
settings = vim.tbl_extend("force", settings, data)
111+
else
112+
vim.notify("Unable to load LSP settings at `" .. path .. "`: invalid JSON", vim.log.levels.WARN)
113+
end
107114
end
108115
end
109116

lua/lspsettings/types.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
---@class lspsettings.types.config
2+
---@field json5 boolean? default true
23
---@field paths string|string[]?
34
---@field on_settings fun(server_name: string, settings: table): nil?
45

test.json5

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"dsada\
3+
assa": 555,
4+
zzz: "abc\
5+
def"
6+
}

0 commit comments

Comments
 (0)