summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.luarc.jsonc (renamed from .luarc.json)0
-rw-r--r--changelog.md1
-rw-r--r--script/client.lua1
-rw-r--r--script/config/loader.lua6
-rw-r--r--script/jsonc.lua603
-rw-r--r--script/provider/provider.lua11
6 files changed, 618 insertions, 4 deletions
diff --git a/.luarc.json b/.luarc.jsonc
index 32fa50a7..32fa50a7 100644
--- a/.luarc.json
+++ b/.luarc.jsonc
diff --git a/changelog.md b/changelog.md
index 372f8b3b..70c469e1 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,7 @@
# changelog
## 3.2.3
+* `CHG` parse `.luarc.json` as jsonc. In order to please the editor, it also supports `.luarc.jsonc` as the file name.
* `CHG` dose not load files in symbol links
* `FIX` diagnostic: send empty results to every file after startup
diff --git a/script/client.lua b/script/client.lua
index 82a01256..d86fb4f2 100644
--- a/script/client.lua
+++ b/script/client.lua
@@ -248,6 +248,7 @@ local function tryModifyRC(uri, finalChanges, create)
end
local workspace = require 'workspace'
local path = workspace.getAbsolutePath(uri, '.luarc.json')
+ or workspace.getAbsolutePath(uri, '.luarc.jsonc')
if not path then
return false
end
diff --git a/script/config/loader.lua b/script/config/loader.lua
index c53f9399..30711dde 100644
--- a/script/config/loader.lua
+++ b/script/config/loader.lua
@@ -1,10 +1,10 @@
-local json = require 'json'
local proto = require 'proto'
local lang = require 'language'
local util = require 'utility'
local workspace = require 'workspace'
local scope = require 'workspace.scope'
local inspect = require 'inspect'
+local jsonc = require 'jsonc'
local function errorMessage(msg)
proto.notify('window/showMessage', {
@@ -29,7 +29,7 @@ function m.loadRCConfig(uri, filename)
scp:set('lastRCConfig', nil)
return nil
end
- local suc, res = pcall(json.decode, buf)
+ local suc, res = pcall(jsonc.decode, buf)
if not suc then
errorMessage(lang.script('CONFIG_LOAD_ERROR', res))
return scp:get('lastRCConfig')
@@ -55,7 +55,7 @@ function m.loadLocalConfig(uri, filename)
end
local firstChar = buf:match '%S'
if firstChar == '{' then
- local suc, res = pcall(json.decode, buf)
+ local suc, res = pcall(jsonc.decode, buf)
if not suc then
errorMessage(lang.script('CONFIG_LOAD_ERROR', res))
return scp:get('lastLocalConfig')
diff --git a/script/jsonc.lua b/script/jsonc.lua
new file mode 100644
index 00000000..0361d99b
--- /dev/null
+++ b/script/jsonc.lua
@@ -0,0 +1,603 @@
+local type = type
+local next = next
+local error = error
+local tonumber = tonumber
+local tostring = tostring
+local table_concat = table.concat
+local table_sort = table.sort
+local string_char = string.char
+local string_byte = string.byte
+local string_find = string.find
+local string_match = string.match
+local string_gsub = string.gsub
+local string_sub = string.sub
+local string_rep = string.rep
+local string_format = string.format
+local setmetatable = setmetatable
+local getmetatable = getmetatable
+local huge = math.huge
+local tiny = -huge
+
+local utf8_char
+local math_type
+
+if _VERSION == "Lua 5.1" or _VERSION == "Lua 5.2" then
+ local math_floor = math.floor
+ function utf8_char(c)
+ if c <= 0x7f then
+ return string_char(c)
+ elseif c <= 0x7ff then
+ return string_char(math_floor(c / 64) + 192, c % 64 + 128)
+ elseif c <= 0xffff then
+ return string_char(
+ math_floor(c / 4096) + 224,
+ math_floor(c % 4096 / 64) + 128,
+ c % 64 + 128
+ )
+ elseif c <= 0x10ffff then
+ return string_char(
+ math_floor(c / 262144) + 240,
+ math_floor(c % 262144 / 4096) + 128,
+ math_floor(c % 4096 / 64) + 128,
+ c % 64 + 128
+ )
+ end
+ error(string.format("invalid UTF-8 code '%x'", c))
+ end
+ function math_type(v)
+ if v >= -2147483648 and v <= 2147483647 and math_floor(v) == v then
+ return "integer"
+ end
+ return "float"
+ end
+else
+ utf8_char = utf8.char
+ math_type = math.type
+end
+
+local json = {}
+
+json.supportSparseArray = true
+
+local objectMt = {}
+
+function json.createEmptyObject()
+ return setmetatable({}, objectMt)
+end
+
+function json.isObject(t)
+ if t[1] ~= nil then
+ return false
+ end
+ return next(t) ~= nil or getmetatable(t) == objectMt
+end
+
+if debug and debug.upvalueid then
+ -- Generate a lightuserdata
+ json.null = debug.upvalueid(json.createEmptyObject, 1)
+else
+ json.null = function() end
+end
+
+-- json.encode --
+
+local statusVisited
+local statusBuilder
+local statusDep
+local statusOpt
+
+local defaultOpt = {
+ newline = "",
+ indent = "",
+}
+defaultOpt.__index = defaultOpt
+
+local encode_map = {}
+
+local encode_escape_map = {
+ [ "\"" ] = "\\\"",
+ [ "\\" ] = "\\\\",
+ [ "/" ] = "\\/",
+ [ "\b" ] = "\\b",
+ [ "\f" ] = "\\f",
+ [ "\n" ] = "\\n",
+ [ "\r" ] = "\\r",
+ [ "\t" ] = "\\t",
+}
+
+local decode_escape_set = {}
+local decode_escape_map = {}
+for k, v in next, encode_escape_map do
+ decode_escape_map[v] = k
+ decode_escape_set[string_byte(v, 2)] = true
+end
+
+for i = 0, 31 do
+ local c = string_char(i)
+ if not encode_escape_map[c] then
+ encode_escape_map[c] = string_format("\\u%04x", i)
+ end
+end
+
+encode_map["nil"] = function ()
+ return "null"
+end
+
+local function encode_string(v)
+ return string_gsub(v, '[%z\1-\31\\"]', encode_escape_map)
+end
+
+local function convertreal(v)
+ local g = string_format('%.16g', v)
+ if tonumber(g) == v then
+ return g
+ end
+ return string_format('%.17g', v)
+end
+
+if string_match(tostring(1/2), "%p") == "," then
+ local _convertreal = convertreal
+ function convertreal(v)
+ return string_gsub(_convertreal(v), ',', '.')
+ end
+end
+
+function encode_map.number(v)
+ if v ~= v or v <= tiny or v >= huge then
+ error("unexpected number value '" .. tostring(v) .. "'")
+ end
+ if math_type(v) == "integer" then
+ return string_format('%d', v)
+ end
+ return convertreal(v)
+end
+
+function encode_map.boolean(v)
+ if v then
+ return "true"
+ else
+ return "false"
+ end
+end
+
+local function encode_unexpected(v)
+ if v == json.null then
+ return "null"
+ else
+ error("unexpected type '"..type(v).."'")
+ end
+end
+encode_map[ "function" ] = encode_unexpected
+encode_map[ "userdata" ] = encode_unexpected
+encode_map[ "thread" ] = encode_unexpected
+
+local function encode_newline()
+ statusBuilder[#statusBuilder+1] = statusOpt.newline..string_rep(statusOpt.indent, statusDep)
+end
+
+local function encode(v)
+ local res = encode_map[type(v)](v)
+ statusBuilder[#statusBuilder+1] = res
+end
+
+function encode_map.string(v)
+ statusBuilder[#statusBuilder+1] = '"'
+ statusBuilder[#statusBuilder+1] = encode_string(v)
+ return '"'
+end
+
+function encode_map.table(t)
+ local first_val = next(t)
+ if first_val == nil then
+ if getmetatable(t) == objectMt then
+ return "{}"
+ else
+ return "[]"
+ end
+ end
+ if statusVisited[t] then
+ error("circular reference")
+ end
+ statusVisited[t] = true
+ if type(first_val) == 'string' then
+ local key = {}
+ for k in next, t do
+ if type(k) ~= "string" then
+ error("invalid table: mixed or invalid key types")
+ end
+ key[#key+1] = k
+ end
+ table_sort(key)
+ statusBuilder[#statusBuilder+1] = "{"
+ statusDep = statusDep + 1
+ encode_newline()
+ local k = key[1]
+ statusBuilder[#statusBuilder+1] = '"'
+ statusBuilder[#statusBuilder+1] = encode_string(k)
+ statusBuilder[#statusBuilder+1] = '": '
+ encode(t[k])
+ for i = 2, #key do
+ local k = key[i]
+ statusBuilder[#statusBuilder+1] = ","
+ encode_newline()
+ statusBuilder[#statusBuilder+1] = '"'
+ statusBuilder[#statusBuilder+1] = encode_string(k)
+ statusBuilder[#statusBuilder+1] = '": '
+ encode(t[k])
+ end
+ statusDep = statusDep - 1
+ encode_newline()
+ statusVisited[t] = nil
+ return "}"
+ elseif json.supportSparseArray then
+ local max = 0
+ for k in next, t do
+ if math_type(k) ~= "integer" or k <= 0 then
+ error("invalid table: mixed or invalid key types")
+ end
+ if max < k then
+ max = k
+ end
+ end
+ statusBuilder[#statusBuilder+1] = "["
+ statusDep = statusDep + 1
+ encode_newline()
+ encode(t[1])
+ for i = 2, max do
+ statusBuilder[#statusBuilder+1] = ","
+ encode_newline()
+ encode(t[i])
+ end
+ statusDep = statusDep - 1
+ encode_newline()
+ statusVisited[t] = nil
+ return "]"
+ else
+ if t[1] == nil then
+ error("invalid table: mixed or invalid key types")
+ end
+ statusBuilder[#statusBuilder+1] = "["
+ statusDep = statusDep + 1
+ encode_newline()
+ encode(t[1])
+ local count = 2
+ while t[count] ~= nil do
+ statusBuilder[#statusBuilder+1] = ","
+ encode_newline()
+ encode(t[count])
+ count = count + 1
+ end
+ if next(t, count-1) ~= nil then
+ error("invalid table: mixed or invalid key types")
+ end
+ statusDep = statusDep - 1
+ encode_newline()
+ statusVisited[t] = nil
+ return "]"
+ end
+end
+
+function json.encode(v, option)
+ statusVisited = {}
+ statusBuilder = {}
+ statusDep = 0
+ statusOpt = option and setmetatable(option, defaultOpt) or defaultOpt
+ encode(v)
+ return table_concat(statusBuilder)
+end
+
+-- json.decode --
+
+local statusBuf
+local statusPos
+local statusTop
+local statusAry = {}
+local statusRef = {}
+
+local function find_line()
+ local line = 1
+ local pos = 1
+ while true do
+ local f, _, nl1, nl2 = string_find(statusBuf, '([\n\r])([\n\r]?)', pos)
+ if not f then
+ return line, statusPos - pos + 1
+ end
+ local newpos = f + ((nl1 == nl2 or nl2 == '') and 1 or 2)
+ if newpos > statusPos then
+ return line, statusPos - pos + 1
+ end
+ pos = newpos
+ line = line + 1
+ end
+end
+
+local function decode_error(msg)
+ error(string_format("ERROR: %s at line %d col %d", msg, find_line()), 2)
+end
+
+local function get_word()
+ return string_match(statusBuf, "^[^ \t\r\n%]},]*", statusPos)
+end
+
+local function skip_comment(b)
+ if b ~= 47 --[[ '/' ]] then
+ return
+ end
+ local c = string_byte(statusBuf, statusPos+1)
+ if c == 42 --[[ '*' ]] then
+ -- block comment
+ local pos = string_find(statusBuf, "*/", statusPos)
+ if pos then
+ statusPos = pos + 2
+ else
+ statusPos = #statusBuf + 1
+ end
+ return true
+ elseif c == 47 --[[ '/' ]] then
+ -- line comment
+ local pos = string_find(statusBuf, "[\r\n]", statusPos)
+ if pos then
+ statusPos = pos
+ else
+ statusPos = #statusBuf + 1
+ end
+ return true
+ end
+end
+
+local function next_byte()
+ local pos = string_find(statusBuf, "[^ \t\r\n]", statusPos)
+ if pos then
+ statusPos = pos
+ local b = string_byte(statusBuf, pos)
+ if not skip_comment(b) then
+ return b
+ end
+ return next_byte()
+ end
+ return -1
+end
+
+local function decode_unicode_surrogate(s1, s2)
+ return utf8_char(0x10000 + (tonumber(s1, 16) - 0xd800) * 0x400 + (tonumber(s2, 16) - 0xdc00))
+end
+
+local function decode_unicode_escape(s)
+ return utf8_char(tonumber(s, 16))
+end
+
+local function decode_string()
+ local has_unicode_escape = false
+ local has_escape = false
+ local i = statusPos + 1
+ while true do
+ i = string_find(statusBuf, '[%z\1-\31\\"]', i)
+ if not i then
+ decode_error "expected closing quote for string"
+ end
+ local x = string_byte(statusBuf, i)
+ if x < 32 then
+ statusPos = i
+ decode_error "control character in string"
+ end
+ if x == 34 --[[ '"' ]] then
+ local s = string_sub(statusBuf, statusPos + 1, i - 1)
+ if has_unicode_escape then
+ s = string_gsub(string_gsub(s
+ , "\\u([dD][89aAbB]%x%x)\\u([dD][c-fC-F]%x%x)", decode_unicode_surrogate)
+ , "\\u(%x%x%x%x)", decode_unicode_escape)
+ end
+ if has_escape then
+ s = string_gsub(s, "\\.", decode_escape_map)
+ end
+ statusPos = i + 1
+ return s
+ end
+ --assert(x == 92 --[[ "\\" ]])
+ local nx = string_byte(statusBuf, i+1)
+ if nx == 117 --[[ "u" ]] then
+ if not string_match(statusBuf, "^%x%x%x%x", i+2) then
+ statusPos = i
+ decode_error "invalid unicode escape in string"
+ end
+ has_unicode_escape = true
+ i = i + 6
+ else
+ if not decode_escape_set[nx] then
+ statusPos = i
+ decode_error("invalid escape char '" .. (nx and string_char(nx) or "<eol>") .. "' in string")
+ end
+ has_escape = true
+ i = i + 2
+ end
+ end
+end
+
+local function decode_number()
+ local num, c = string_match(statusBuf, '^([0-9]+%.?[0-9]*)([eE]?)', statusPos)
+ if not num or string_byte(num, -1) == 0x2E --[[ "." ]] then
+ decode_error("invalid number '" .. get_word() .. "'")
+ end
+ if c ~= '' then
+ num = string_match(statusBuf, '^([^eE]*[eE][-+]?[0-9]+)[ \t\r\n%]},/]', statusPos)
+ if not num then
+ decode_error("invalid number '" .. get_word() .. "'")
+ end
+ end
+ statusPos = statusPos + #num
+ return tonumber(num)
+end
+
+local function decode_number_zero()
+ local num, c = string_match(statusBuf, '^(.%.?[0-9]*)([eE]?)', statusPos)
+ if not num or string_byte(num, -1) == 0x2E --[[ "." ]] or string_match(statusBuf, '^.[0-9]+', statusPos) then
+ decode_error("invalid number '" .. get_word() .. "'")
+ end
+ if c ~= '' then
+ num = string_match(statusBuf, '^([^eE]*[eE][-+]?[0-9]+)[ \t\r\n%]},/]', statusPos)
+ if not num then
+ decode_error("invalid number '" .. get_word() .. "'")
+ end
+ end
+ statusPos = statusPos + #num
+ return tonumber(num)
+end
+
+local function decode_number_negative()
+ statusPos = statusPos + 1
+ local c = string_byte(statusBuf, statusPos)
+ if c then
+ if c == 0x30 then
+ return -decode_number_zero()
+ elseif c > 0x30 and c < 0x3A then
+ return -decode_number()
+ end
+ end
+ decode_error("invalid number '" .. get_word() .. "'")
+end
+
+local function decode_true()
+ if string_sub(statusBuf, statusPos, statusPos+3) ~= "true" then
+ decode_error("invalid literal '" .. get_word() .. "'")
+ end
+ statusPos = statusPos + 4
+ return true
+end
+
+local function decode_false()
+ if string_sub(statusBuf, statusPos, statusPos+4) ~= "false" then
+ decode_error("invalid literal '" .. get_word() .. "'")
+ end
+ statusPos = statusPos + 5
+ return false
+end
+
+local function decode_null()
+ if string_sub(statusBuf, statusPos, statusPos+3) ~= "null" then
+ decode_error("invalid literal '" .. get_word() .. "'")
+ end
+ statusPos = statusPos + 4
+ return json.null
+end
+
+local function decode_array()
+ statusPos = statusPos + 1
+ local res = {}
+ local chr = next_byte()
+ if chr == 93 --[[ ']' ]] then
+ statusPos = statusPos + 1
+ return res
+ end
+ statusTop = statusTop + 1
+ statusAry[statusTop] = true
+ statusRef[statusTop] = res
+ return res
+end
+
+local function decode_object()
+ statusPos = statusPos + 1
+ local res = {}
+ local chr = next_byte()
+ if chr == 125 --[[ ']' ]] then
+ statusPos = statusPos + 1
+ return json.createEmptyObject()
+ end
+ statusTop = statusTop + 1
+ statusAry[statusTop] = false
+ statusRef[statusTop] = res
+ return res
+end
+
+local decode_uncompleted_map = {
+ [ string_byte '"' ] = decode_string,
+ [ string_byte "0" ] = decode_number_zero,
+ [ string_byte "1" ] = decode_number,
+ [ string_byte "2" ] = decode_number,
+ [ string_byte "3" ] = decode_number,
+ [ string_byte "4" ] = decode_number,
+ [ string_byte "5" ] = decode_number,
+ [ string_byte "6" ] = decode_number,
+ [ string_byte "7" ] = decode_number,
+ [ string_byte "8" ] = decode_number,
+ [ string_byte "9" ] = decode_number,
+ [ string_byte "-" ] = decode_number_negative,
+ [ string_byte "t" ] = decode_true,
+ [ string_byte "f" ] = decode_false,
+ [ string_byte "n" ] = decode_null,
+ [ string_byte "[" ] = decode_array,
+ [ string_byte "{" ] = decode_object,
+}
+local function unexpected_character()
+ decode_error("unexpected character '" .. string_sub(statusBuf, statusPos, statusPos) .. "'")
+end
+local function unexpected_eol()
+ decode_error("unexpected character '<eol>'")
+end
+
+local decode_map = {}
+for i = 0, 255 do
+ decode_map[i] = decode_uncompleted_map[i] or unexpected_character
+end
+decode_map[-1] = unexpected_eol
+
+local function decode()
+ return decode_map[next_byte()]()
+end
+
+local function decode_item()
+ local top = statusTop
+ local ref = statusRef[top]
+ if statusAry[top] then
+ ref[#ref+1] = decode()
+ else
+ local key = decode_string()
+ if next_byte() ~= 58 --[[ ':' ]] then
+ decode_error "expected ':'"
+ end
+ statusPos = statusPos + 1
+ ref[key] = decode()
+ end
+ if top == statusTop then
+ repeat
+ local chr = next_byte(); statusPos = statusPos + 1
+ if chr == 44 --[[ "," ]] then
+ local c = next_byte()
+ if statusAry[statusTop] then
+ if c ~= 93 --[[ "]" ]] then return end
+ else
+ if c ~= 125 --[[ "}" ]] then return end
+ end
+ statusPos = statusPos + 1
+ else
+ if statusAry[statusTop] then
+ if chr ~= 93 --[[ "]" ]] then decode_error "expected ']' or ','" end
+ else
+ if chr ~= 125 --[[ "}" ]] then decode_error "expected '}' or ','" end
+ end
+ end
+ statusTop = statusTop - 1
+ until statusTop == 0
+ end
+end
+
+function json.decode(str)
+ if type(str) ~= "string" then
+ error("expected argument of type string, got " .. type(str))
+ end
+ statusBuf = str
+ statusPos = 1
+ statusTop = 0
+ if next_byte() == -1 then
+ return json.null
+ end
+ local res = decode()
+ while statusTop > 0 do
+ decode_item()
+ end
+ if string_find(statusBuf, "[^ \t\r\n]", statusPos) then
+ decode_error "trailing garbage"
+ end
+ return res
+end
+
+return json
diff --git a/script/provider/provider.lua b/script/provider/provider.lua
index 6c87379e..08b6ca93 100644
--- a/script/provider/provider.lua
+++ b/script/provider/provider.lua
@@ -42,8 +42,9 @@ local function updateConfig(uri)
end
local rc = cfgLoader.loadRCConfig(folder.uri, '.luarc.json')
+ or cfgLoader.loadRCConfig(folder.uri, '.luarc.jsonc')
if rc then
- log.info('Load config from luarc.json', folder.uri)
+ log.info('Load config from .luarc.json/.luarc.jsonc', folder.uri)
log.debug(inspect(rc))
end
@@ -91,6 +92,14 @@ filewatch.event(function (ev, path) ---@async
end
end
end
+ if util.stringEndWith(path, '.luarc.jsonc') then
+ for _, scp in ipairs(workspace.folders) do
+ local rcPath = workspace.getAbsolutePath(scp.uri, '.luarc.jsonc')
+ if path == rcPath then
+ updateConfig(scp.uri)
+ end
+ end
+ end
end)
m.register 'initialize' {